--- /srv/rebuilderd/tmp/rebuilderdx895ru/inputs/erlang-doc_27.3.4.4+dfsg-1_all.deb +++ /srv/rebuilderd/tmp/rebuilderdx895ru/out/erlang-doc_27.3.4.4+dfsg-1_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2025-10-31 11:57:11.000000 debian-binary │ --rw-r--r-- 0 0 0 39836 2025-10-31 11:57:11.000000 control.tar.xz │ --rw-r--r-- 0 0 0 16799524 2025-10-31 11:57:11.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 39820 2025-10-31 11:57:11.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 16800180 2025-10-31 11:57:11.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./control │ │ │ @@ -1,13 +1,13 @@ │ │ │ Package: erlang-doc │ │ │ Source: erlang │ │ │ Version: 1:27.3.4.4+dfsg-1 │ │ │ Architecture: all │ │ │ Maintainer: Debian Erlang Packagers │ │ │ -Installed-Size: 96105 │ │ │ +Installed-Size: 96104 │ │ │ Depends: libjs-jquery, libjs-jquery-ui │ │ │ Suggests: erlang:any │ │ │ Conflicts: erlang-base:any (<< 1:13.b.4), erlang-base-hipe:any, erlang-doc-html │ │ │ Replaces: erlang-doc-html │ │ │ Provides: erlang-doc-html │ │ │ Section: doc │ │ │ Priority: optional │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -129,15 +129,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/doc/system/dist/search_data-C27850F8.js │ │ │ │ +usr/share/doc/erlang-doc/html/doc/system/dist/search_data-62230FA7.js │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/dist/sidebar_items-4A143270.js │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/distributed.html │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/distributed_applications.html │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/documentation.html │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/drivers.html │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/eff_guide_functions.html │ │ │ │ usr/share/doc/erlang-doc/html/doc/system/eff_guide_processes.html │ │ │ │ @@ -516,15 +516,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-6D666814.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-0488DFD4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/sidebar_items-F976ACE0.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/typer_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/.build │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/404.html │ │ │ │ @@ -608,15 +608,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/search_data-24CC7BE0.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/search_data-76AA0443.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/sidebar_items-4C553487.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/doc_storage.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet_chunks.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet_markdown.html │ │ │ │ @@ -781,15 +781,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-D71AAFA6.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-FF6A1601.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/sidebar_items-B6B07F6E.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp_client.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/notes.html │ │ │ │ @@ -811,15 +811,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/search_data-64C33046.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/search_data-DD625D80.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/sidebar_items-BBD1F630.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_client.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_server.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_uri.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpc.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd_custom_api.html │ │ │ │ @@ -971,15 +971,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/search_data-FF5C73C8.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/search_data-11DD7C5B.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/sidebar_items-F81AEB6B.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/eep48_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_boot_server.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_ddll.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_epmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erpc.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/error_handler.html │ │ │ │ @@ -1093,15 +1093,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/search_data-48CBC29D.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/search_data-669DC705.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/sidebar_items-845AA6F8.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_a.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_b.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_c.html │ │ │ │ @@ -1139,15 +1139,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-C92E6517.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-FFC02264.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/sidebar_items-6D9D41B7.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/introduction_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ │ @@ -1176,15 +1176,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-465DEDF7.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-D78563F6.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/sidebar_items-19ECDBA9.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/error_handling.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/getting_started.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ │ @@ -1269,15 +1269,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/search_data-3C8D465E.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/search_data-A4619FA2.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/sidebar_items-6446AF99.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key_app.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key_records.html │ │ │ │ @@ -1300,15 +1300,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/search_data-8726EDEC.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/search_data-C5321E03.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/sidebar_items-DF937488.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_examples.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_intro.html │ │ │ │ @@ -1332,15 +1332,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-A1188F1F.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-1DC1D27C.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/sidebar_items-E5CACCF4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dtrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dyntrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/instrument.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/lttng.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/msacc.html │ │ │ │ @@ -1542,15 +1542,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/search_data-2DAE4F8E.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/search_data-31FB6662.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/sidebar_items-617A7806.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_app.html │ │ │ │ @@ -1684,15 +1684,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/search_data-1D7BB8C0.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/search_data-B09DB05B.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/sidebar_items-2B4A1108.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/epp_dodger.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_comment_scan.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_prettypr.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_recomment.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_syntax.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_syntax_lib.html │ │ │ │ @@ -1756,15 +1756,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/search_data-074D3797.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/search_data-6515C1E6.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/sidebar_items-11035E81.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/eprof.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/erlang-el.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/erlang_mode_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/fprof.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/fprof_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/index.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -137,15 +137,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 292 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 293 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 294 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 300 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2286 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5648 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 654589 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 654601 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53542 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 97489 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -181,15 +181,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 1016759 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/search_data-C27850F8.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 1016759 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/search_data-62230FA7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 32130 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/dist/sidebar_items-4A143270.js │ │ │ -rw-r--r-- 0 root (0) root (0) 30039 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/distributed.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20667 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/distributed_applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52930 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/documentation.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15036 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/drivers.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26945 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_functions.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52169 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_processes.html │ │ │ @@ -351,15 +351,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1060 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6692 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/api-reference.html │ │ │ --rw-r--r-- 0 root (0) root (0) 96847 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 96859 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 140490 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9328 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7454 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_overview.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78800 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_spec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35477 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1ct.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1340 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/assets/exclusive_Win_But.gif │ │ │ @@ -397,15 +397,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 10672 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 4963 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/config.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 10726 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/html_logs.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 9561 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/tc_execution.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 21795 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/basics_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 399364 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 399358 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7502 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59626 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/config_file_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25541 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/cover_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 182818 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12310 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_cover.html │ │ │ -rw-r--r-- 0 root (0) root (0) 30032 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77362 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_hooks.html │ │ │ @@ -500,15 +500,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 992 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6016 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35139 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/algorithm_details.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6670 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 127050 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/crypto.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 127049 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/crypto.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 294963 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/crypto.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10018 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/crypto_app.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -546,15 +546,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 21770 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/cond_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 13532 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/function_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 28924 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/interpret.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 14414 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/line_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 40742 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/monitor.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 34504 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/view.jpg │ │ │ --rw-r--r-- 0 root (0) root (0) 219444 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 219450 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13135 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52048 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -604,29 +604,29 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 122270 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-6D666814.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 122270 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-0488DFD4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7336 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/sidebar_items-F976ACE0.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 132207 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5944 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10876 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/typer_cmd.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1143 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6034 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8222 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 144222 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 144212 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 253956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57599 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29032 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_codec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 32274 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_dict.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6784 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9532 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21968 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_make.html │ │ │ @@ -707,15 +707,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 108335 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/search_data-24CC7BE0.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 108335 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/search_data-76AA0443.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12810 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/dist/sidebar_items-4C553487.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12682 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/doc_storage.html │ │ │ -rw-r--r-- 0 root (0) root (0) 58180 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8098 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_cmd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 16455 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8636 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet_chunks.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10093 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/html/edoc_doclet_markdown.html │ │ │ @@ -789,15 +789,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 193099 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-1F14090C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 15936 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/sidebar_items-8A5CCEF3.js │ │ │ -rw-r--r-- 0 root (0) root (0) 73740 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei.html │ │ │ -rw-r--r-- 0 root (0) root (0) 72795 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_connect.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11739 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26982 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_users_guide.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22688 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_call_cmd.html │ │ │ --rw-r--r-- 0 root (0) root (0) 84945 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_interface.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 84947 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_interface.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 272 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110953 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5565 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1332 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/.build │ │ │ @@ -832,15 +832,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 80722 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/search_data-07FF68FB.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9243 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/sidebar_items-639C3385.js │ │ │ --rw-r--r-- 0 root (0) root (0) 302542 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 302545 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 22899 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57168 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_collector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52414 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_desc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100534 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9916 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20457 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_selector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45778 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_tutorial.html │ │ │ @@ -905,17 +905,17 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 29403 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-D71AAFA6.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 29403 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-FF6A1601.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5270 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/sidebar_items-B6B07F6E.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33164 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33166 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 82127 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12856 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7162 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22515 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/ │ │ │ @@ -940,26 +940,26 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 219844 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/search_data-64C33046.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 219844 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/search_data-DD625D80.js │ │ │ -rw-r--r-- 0 root (0) root (0) 22975 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/dist/sidebar_items-BBD1F630.js │ │ │ -rw-r--r-- 0 root (0) root (0) 27795 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 56380 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_server.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11421 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/http_uri.html │ │ │ -rw-r--r-- 0 root (0) root (0) 91850 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 117910 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12136 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd_custom_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13448 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd_socket.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45124 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/httpd_util.html │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 152853 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/inets.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 152855 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/inets.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 25717 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/inets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8659 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/inets_services.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7466 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21300 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/mod_alias.html │ │ │ -rw-r--r-- 0 root (0) root (0) 83168 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/mod_auth.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19815 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/mod_esi.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37282 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.1/doc/html/mod_security.html │ │ │ @@ -1118,15 +1118,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 1220988 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/search_data-FF5C73C8.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 1220988 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/search_data-11DD7C5B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 115664 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/dist/sidebar_items-F81AEB6B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 21084 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/eep48_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15895 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_boot_server.html │ │ │ -rw-r--r-- 0 root (0) root (0) 85756 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_ddll.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25156 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erl_epmd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111330 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/erpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15177 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/error_handler.html │ │ │ @@ -1138,15 +1138,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57284 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37262 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/global_group.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24987 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/heart.html │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 184609 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/inet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 86882 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/inet_res.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7733 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/introduction_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 790884 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/kernel.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 790892 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/kernel.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 42777 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/kernel_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 188467 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 108811 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70518 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger_cookbook.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15657 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger_disk_log_h.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25586 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger_filters.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34203 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.2/doc/html/logger_formatter.html │ │ │ @@ -1198,15 +1198,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 200183 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/search_data-3F59FB08.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33244 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/dist/sidebar_items-0FDD3384.js │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 181537 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 181545 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 199461 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13680 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_architecture.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9136 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_codec_meas.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23098 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_codec_mstone1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9740 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_codec_mstone2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9712 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_codec_transform.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18676 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2/doc/html/megaco_debug.html │ │ │ @@ -1250,18 +1250,18 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 375315 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/search_data-48CBC29D.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 375315 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/search_data-669DC705.js │ │ │ -rw-r--r-- 0 root (0) root (0) 24530 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/dist/sidebar_items-845AA6F8.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 221930 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 221934 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 320920 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45468 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_a.html │ │ │ -rw-r--r-- 0 root (0) root (0) 87795 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_b.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46060 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_app_c.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9869 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_chap1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109095 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_chap2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51394 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5/doc/html/mnesia_chap3.html │ │ │ @@ -1301,22 +1301,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 146200 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-C92E6517.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 146200 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-FFC02264.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12765 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/sidebar_items-6D9D41B7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17966 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15746 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7350 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/introduction_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 71142 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 116872 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 116859 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13905 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7238 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23494 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5941 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111935 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/ttb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 165007 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/ttb_ug.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/ │ │ │ @@ -1343,22 +1343,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 76343 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-465DEDF7.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 76343 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-D78563F6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7406 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/sidebar_items-19ECDBA9.js │ │ │ -rw-r--r-- 0 root (0) root (0) 13859 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/error_handling.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51373 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8466 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57071 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 67277 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 67291 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 76660 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6019 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/404.html │ │ │ @@ -1422,15 +1422,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 55131 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/search_data-5B3B164D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5679 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/sidebar_items-E70C9F62.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 55824 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/leex.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37754 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 44439 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 44434 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/parsetools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 5950 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67892 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/yecc.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6049 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/404.html │ │ │ @@ -1451,19 +1451,19 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 145165 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/search_data-3C8D465E.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 145165 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/search_data-A4619FA2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 16466 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/dist/sidebar_items-6446AF99.js │ │ │ -rw-r--r-- 0 root (0) root (0) 271 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 90723 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 100056 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 100045 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 207172 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10281 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70652 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/public_key_records.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5965 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 131335 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.1/doc/html/using_public_key.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/ │ │ │ @@ -1487,19 +1487,19 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 90283 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/search_data-8726EDEC.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 90283 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/search_data-C5321E03.js │ │ │ -rw-r--r-- 0 root (0) root (0) 8840 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/sidebar_items-DF937488.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46277 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 62891 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 62892 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 100617 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.html │ │ │ -rw-r--r-- 0 root (0) root (0) 199744 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23138 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_usage.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/ │ │ │ @@ -1524,24 +1524,24 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 161570 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-A1188F1F.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 161570 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-1DC1D27C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 18284 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/sidebar_items-E5CACCF4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9774 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dtrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47868 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dyntrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 271 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50868 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/instrument.html │ │ │ -rw-r--r-- 0 root (0) root (0) 64811 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/lttng.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50067 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/msacc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78751 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 118757 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 118750 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7584 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29115 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/scheduler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5974 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12911 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/system_information.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9992 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/systemtap.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/ │ │ │ @@ -1576,15 +1576,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 34663 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/error_logging.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70067 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 42546 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/rb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12220 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/rel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 80519 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/release_handler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9577 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/relup.html │ │ │ --rw-r--r-- 0 root (0) root (0) 92274 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/sasl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 92272 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/sasl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 17193 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/sasl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7699 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/sasl_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17269 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/script.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5920 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 40703 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2/doc/html/systools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/ │ │ │ @@ -1623,15 +1623,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 549455 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/search_data-18B48D27.js │ │ │ -rw-r--r-- 0 root (0) root (0) 90029 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/sidebar_items-E4326166.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 61226 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 443714 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 443719 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 148375 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39983 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_advanced_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62881 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_config_files.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51341 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_funct_descr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18123 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_netif.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66609 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8609 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_app_a.html │ │ │ @@ -1714,15 +1714,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 368072 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/dist/search_data-E0401131.js │ │ │ -rw-r--r-- 0 root (0) root (0) 45936 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/dist/sidebar_items-F32BF3C6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 23928 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/hardening.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14206 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 231444 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 272661 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 272660 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 250454 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24859 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25480 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44362 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_client_channel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23335 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_client_key_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78399 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_connection.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49063 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.3/doc/html/ssh_file.html │ │ │ @@ -1754,20 +1754,20 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 486276 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/search_data-2DAE4F8E.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 486276 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/search_data-31FB6662.js │ │ │ -rw-r--r-- 0 root (0) root (0) 27242 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/dist/sidebar_items-617A7806.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 254708 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 210317 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 210318 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 316851 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17362 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12890 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_crl_cache.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21815 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_crl_cache_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_distribution.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14213 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25887 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.3/doc/html/ssl_session_cache_api.html │ │ │ @@ -1865,15 +1865,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47248 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/sets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106196 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/shell.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10144 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/shell_default.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49822 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/shell_docs.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33204 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/slave.html │ │ │ -rw-r--r-- 0 root (0) root (0) 354762 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/sofs.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1410495 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/stdlib.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1410466 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/stdlib.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 15682 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/stdlib_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 191516 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/string.html │ │ │ -rw-r--r-- 0 root (0) root (0) 93086 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/supervisor.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20623 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/supervisor_bridge.html │ │ │ -rw-r--r-- 0 root (0) root (0) 107262 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/sys.html │ │ │ -rw-r--r-- 0 root (0) root (0) 81680 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/timer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 75028 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.2/doc/html/unicode.html │ │ │ @@ -1906,15 +1906,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 212519 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/search_data-1D7BB8C0.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 212519 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/search_data-B09DB05B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 52682 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/dist/sidebar_items-2B4A1108.js │ │ │ -rw-r--r-- 0 root (0) root (0) 37822 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/epp_dodger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17453 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_comment_scan.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39814 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_prettypr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17404 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_recomment.html │ │ │ -rw-r--r-- 0 root (0) root (0) 509513 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_syntax.html │ │ │ -rw-r--r-- 0 root (0) root (0) 112150 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/syntax_tools-3.2.2.2/doc/html/erl_syntax_lib.html │ │ │ @@ -1953,15 +1953,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 22267 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/dist/search_data-7B342769.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3043 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/dist/sidebar_items-3CBBBF05.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9543 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7929 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14778 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5920 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 28826 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/tftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 28823 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/tftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 44679 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/tftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11777 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2/doc/html/tftp_logger.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1139 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/404.html │ │ │ @@ -1988,29 +1988,29 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 305159 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/search_data-074D3797.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 305159 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/search_data-6515C1E6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 36457 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/dist/sidebar_items-11035E81.js │ │ │ -rw-r--r-- 0 root (0) root (0) 42782 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/eprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28459 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/erlang-el.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18986 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/erlang_mode_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 132045 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/fprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12996 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/fprof_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67398 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/lcnt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 53417 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/lcnt_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18424 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106923 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28575 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 239549 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 239541 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 173731 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 184557 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/xref.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39616 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/xref_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1612 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/.build.gz │ │ │ @@ -2040,15 +2040,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1664774 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/dist/search_data-7BF0929D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 578643 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/dist/sidebar_items-A20B6997.js │ │ │ -rw-r--r-- 0 root (0) root (0) 1720397 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/gl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77340 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/glu.html │ │ │ -rw-r--r-- 0 root (0) root (0) 260 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 58699 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1607143 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wx.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1607146 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wx.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 54017 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wx.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19452 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxAcceleratorEntry.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15130 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxAcceleratorTable.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12373 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxActivateEvent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19127 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxArtProvider.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17639 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxAuiDockArt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 63043 2025-10-31 11:57:11.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3/doc/html/wxAuiManager.html │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ @@ -37,15 +37,15 @@ │ │ │ dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ dist/remixicon-NKANDIL5.woff2 │ │ │ -dist/search_data-C27850F8.js │ │ │ +dist/search_data-62230FA7.js │ │ │ dist/sidebar_items-4A143270.js │ │ │ distributed.html │ │ │ distributed_applications.html │ │ │ documentation.html │ │ │ drivers.html │ │ │ eff_guide_functions.html │ │ │ eff_guide_processes.html │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ ├── zipinfo {} │ │ │ │ @@ -1,93 +1,93 @@ │ │ │ │ -Zip file size: 654589 bytes, number of entries: 91 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 25-Oct-31 13:18 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 17922 bx defN 25-Oct-31 13:18 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4673 bx defN 25-Oct-31 13:18 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53439 bx defN 25-Oct-31 13:18 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2166 bx defN 25-Oct-31 13:18 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 764 bx defN 25-Oct-31 13:18 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46256 bx defN 25-Oct-31 13:18 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12466 bx defN 25-Oct-31 13:18 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7346 bx defN 25-Oct-31 13:18 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63476 bx defN 25-Oct-31 13:18 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 253918 bx defN 25-Oct-31 13:18 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111264 bx defN 25-Oct-31 13:18 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 249951 bx defN 25-Oct-31 13:18 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70943 bx defN 25-Oct-31 13:18 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20854 bx defN 25-Oct-31 13:18 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 59888 bx defN 25-Oct-31 13:18 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4596 bx defN 25-Oct-31 13:18 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19455 bx defN 25-Oct-31 13:18 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 48276 bx defN 25-Oct-31 13:18 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14454 bx defN 25-Oct-31 13:18 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49542 bx defN 25-Oct-31 13:18 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2190 bx defN 25-Oct-31 13:18 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 785 bx defN 25-Oct-31 13:18 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40147 bx defN 25-Oct-31 13:18 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15206 bx defN 25-Oct-31 13:18 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8501 bx defN 25-Oct-31 13:18 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3737 bx defN 25-Oct-31 13:18 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13417 bx defN 25-Oct-31 13:18 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8959 bx defN 25-Oct-31 13:18 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9082 bx defN 25-Oct-31 13:18 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14065 bx defN 25-Oct-31 13:18 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6367 bx defN 25-Oct-31 13:18 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 25842 bx defN 25-Oct-31 13:18 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7012 bx defN 25-Oct-31 13:18 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5477 bx defN 25-Oct-31 13:18 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 45509 bx defN 25-Oct-31 13:18 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39594 bx defN 25-Oct-31 13:18 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31439 bx defN 25-Oct-31 13:18 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 42996 bx defN 25-Oct-31 13:18 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2211 bx defN 25-Oct-31 13:18 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 55516 bx defN 25-Oct-31 13:18 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28229 bx defN 25-Oct-31 13:18 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35715 bx defN 25-Oct-31 13:18 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20858 bx defN 25-Oct-31 13:18 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2353 bx defN 25-Oct-31 13:18 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31341 bx defN 25-Oct-31 13:18 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 118675 bx defN 25-Oct-31 13:18 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8453 bx defN 25-Oct-31 13:18 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 255000 bx defN 25-Oct-31 13:18 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2365 bx defN 25-Oct-31 13:18 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26747 bx defN 25-Oct-31 13:18 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16629 bx defN 25-Oct-31 13:18 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13609 bx defN 25-Oct-31 13:18 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 42501 bx defN 25-Oct-31 13:18 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18220 bx defN 25-Oct-31 13:18 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2085 bx defN 25-Oct-31 13:18 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46447 bx defN 25-Oct-31 13:18 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21209 bx defN 25-Oct-31 13:18 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9338 bx defN 25-Oct-31 13:18 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47180 bx defN 25-Oct-31 13:18 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14892 bx defN 25-Oct-31 13:18 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24284 bx defN 25-Oct-31 13:18 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14562 bx defN 25-Oct-31 13:18 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 25-Oct-31 13:18 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36780 bx defN 25-Oct-31 13:18 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15003 bx defN 25-Oct-31 13:18 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 71771 bx defN 25-Oct-31 13:18 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 115067 bx defN 25-Oct-31 13:18 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13187 bx defN 25-Oct-31 13:18 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 129900 bx defN 25-Oct-31 13:18 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 33231 bx defN 25-Oct-31 13:18 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11935 bx defN 25-Oct-31 13:18 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 803 bx defN 25-Oct-31 13:18 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5177 bx defN 25-Oct-31 13:18 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40707 bx defN 25-Oct-31 13:18 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35510 bx defN 25-Oct-31 13:18 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34819 bx defN 25-Oct-31 13:18 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53327 bx defN 25-Oct-31 13:18 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7606 bx defN 25-Oct-31 13:18 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 25-Oct-31 13:18 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 25-Oct-31 13:18 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 25-Oct-31 13:18 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 25-Oct-31 13:18 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 25-Oct-31 13:18 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 25-Oct-31 13:18 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 25-Oct-31 13:18 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 25-Oct-31 13:18 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 91721 bx defN 25-Oct-31 13:18 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47722 bx defN 25-Oct-31 13:18 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 25-Oct-31 13:18 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 25-Oct-31 13:18 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -91 files, 3077736 bytes uncompressed, 638657 bytes compressed: 79.3% │ │ │ │ +Zip file size: 654601 bytes, number of entries: 91 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 25-Nov-02 00:52 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 17922 bx defN 25-Nov-02 00:52 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4673 bx defN 25-Nov-02 00:52 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53439 bx defN 25-Nov-02 00:52 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2166 bx defN 25-Nov-02 00:52 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 764 bx defN 25-Nov-02 00:52 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46256 bx defN 25-Nov-02 00:52 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12466 bx defN 25-Nov-02 00:52 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7346 bx defN 25-Nov-02 00:52 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63476 bx defN 25-Nov-02 00:52 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 253918 bx defN 25-Nov-02 00:52 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111264 bx defN 25-Nov-02 00:52 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 249951 bx defN 25-Nov-02 00:52 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70943 bx defN 25-Nov-02 00:52 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20854 bx defN 25-Nov-02 00:52 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 59888 bx defN 25-Nov-02 00:52 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4596 bx defN 25-Nov-02 00:52 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19455 bx defN 25-Nov-02 00:52 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 48276 bx defN 25-Nov-02 00:52 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14454 bx defN 25-Nov-02 00:52 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49542 bx defN 25-Nov-02 00:52 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2190 bx defN 25-Nov-02 00:52 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 785 bx defN 25-Nov-02 00:52 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40147 bx defN 25-Nov-02 00:52 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15206 bx defN 25-Nov-02 00:52 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8501 bx defN 25-Nov-02 00:52 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3737 bx defN 25-Nov-02 00:52 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13417 bx defN 25-Nov-02 00:52 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8959 bx defN 25-Nov-02 00:52 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9082 bx defN 25-Nov-02 00:52 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14065 bx defN 25-Nov-02 00:52 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6367 bx defN 25-Nov-02 00:52 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 25842 bx defN 25-Nov-02 00:52 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7012 bx defN 25-Nov-02 00:52 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5477 bx defN 25-Nov-02 00:52 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 45509 bx defN 25-Nov-02 00:52 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39594 bx defN 25-Nov-02 00:52 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31439 bx defN 25-Nov-02 00:52 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 42996 bx defN 25-Nov-02 00:52 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2211 bx defN 25-Nov-02 00:52 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 55516 bx defN 25-Nov-02 00:52 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28229 bx defN 25-Nov-02 00:52 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35715 bx defN 25-Nov-02 00:52 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20858 bx defN 25-Nov-02 00:52 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2353 bx defN 25-Nov-02 00:52 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31341 bx defN 25-Nov-02 00:52 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 118675 bx defN 25-Nov-02 00:52 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8453 bx defN 25-Nov-02 00:52 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 255000 bx defN 25-Nov-02 00:52 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2365 bx defN 25-Nov-02 00:52 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26747 bx defN 25-Nov-02 00:52 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16629 bx defN 25-Nov-02 00:52 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13609 bx defN 25-Nov-02 00:52 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 42501 bx defN 25-Nov-02 00:52 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18220 bx defN 25-Nov-02 00:52 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2085 bx defN 25-Nov-02 00:52 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46447 bx defN 25-Nov-02 00:52 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21209 bx defN 25-Nov-02 00:52 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9338 bx defN 25-Nov-02 00:52 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47180 bx defN 25-Nov-02 00:52 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14892 bx defN 25-Nov-02 00:52 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24284 bx defN 25-Nov-02 00:52 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14562 bx defN 25-Nov-02 00:52 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 25-Nov-02 00:52 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36780 bx defN 25-Nov-02 00:52 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15003 bx defN 25-Nov-02 00:52 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 71771 bx defN 25-Nov-02 00:52 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 115067 bx defN 25-Nov-02 00:52 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13187 bx defN 25-Nov-02 00:52 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 129900 bx defN 25-Nov-02 00:52 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 33231 bx defN 25-Nov-02 00:52 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11935 bx defN 25-Nov-02 00:52 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 803 bx defN 25-Nov-02 00:52 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5177 bx defN 25-Nov-02 00:52 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40707 bx defN 25-Nov-02 00:52 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35510 bx defN 25-Nov-02 00:52 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34819 bx defN 25-Nov-02 00:52 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53327 bx defN 25-Nov-02 00:52 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7606 bx defN 25-Nov-02 00:52 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 25-Nov-02 00:52 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 25-Nov-02 00:52 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 25-Nov-02 00:52 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 25-Nov-02 00:52 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 25-Nov-02 00:52 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 25-Nov-02 00:52 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 25-Nov-02 00:52 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 25-Nov-02 00:52 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 91721 bx defN 25-Nov-02 00:52 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47722 bx defN 25-Nov-02 00:52 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 25-Nov-02 00:52 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 25-Nov-02 00:52 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +91 files, 3077736 bytes uncompressed, 638669 bytes compressed: 79.3% │ │ │ ├── zipdetails --redact --walk --utc {} │ │ │ │ @@ -1,29 +1,29 @@ │ │ │ │ │ │ │ │ 00000 LOCAL HEADER #1 04034B50 (67324752) │ │ │ │ 00004 Extract Zip Spec 0A (10) '1.0' │ │ │ │ 00005 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 00006 General Purpose Flag 0000 (0) │ │ │ │ 00008 Compression Method 0000 (0) 'Stored' │ │ │ │ -0000A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ +0000A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ 0000E CRC 2CAB616F (749429103) │ │ │ │ 00012 Compressed Size 00000014 (20) │ │ │ │ 00016 Uncompressed Size 00000014 (20) │ │ │ │ 0001A Filename Length 0008 (8) │ │ │ │ 0001C Extra Length 001C (28) │ │ │ │ 0001E Filename 'XXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x1E: Filename 'XXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 00026 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 00028 Length 0009 (9) │ │ │ │ 0002A Flags 03 (3) 'Modification Access' │ │ │ │ -0002B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -0002F Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +0002B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +0002F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 00033 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 00035 Length 000B (11) │ │ │ │ 00037 Version 01 (1) │ │ │ │ 00038 UID Size 04 (4) │ │ │ │ 00039 UID 00000000 (0) │ │ │ │ 0003D GID Size 04 (4) │ │ │ │ 0003E GID 00000000 (0) │ │ │ │ @@ -31,30 +31,30 @@ │ │ │ │ │ │ │ │ 00056 LOCAL HEADER #2 04034B50 (67324752) │ │ │ │ 0005A Extract Zip Spec 14 (20) '2.0' │ │ │ │ 0005B Extract OS 00 (0) 'MS-DOS' │ │ │ │ 0005C General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 0005E Compression Method 0008 (8) 'Deflated' │ │ │ │ -00060 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ +00060 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ 00064 CRC 203D4469 (540886121) │ │ │ │ 00068 Compressed Size 000015AD (5549) │ │ │ │ 0006C Uncompressed Size 00004602 (17922) │ │ │ │ 00070 Filename Length 0014 (20) │ │ │ │ 00072 Extra Length 001C (28) │ │ │ │ 00074 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x74: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 00088 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 0008A Length 0009 (9) │ │ │ │ 0008C Flags 03 (3) 'Modification Access' │ │ │ │ -0008D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -00091 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +0008D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +00091 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 00095 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 00097 Length 000B (11) │ │ │ │ 00099 Version 01 (1) │ │ │ │ 0009A UID Size 04 (4) │ │ │ │ 0009B UID 00000000 (0) │ │ │ │ 0009F GID Size 04 (4) │ │ │ │ 000A0 GID 00000000 (0) │ │ │ │ @@ -62,30 +62,30 @@ │ │ │ │ │ │ │ │ 01651 LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ 01655 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 01656 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 01657 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 01659 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0165B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ +0165B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ 0165F CRC 1EC1D7C5 (516020165) │ │ │ │ 01663 Compressed Size 000006D5 (1749) │ │ │ │ 01667 Uncompressed Size 00001241 (4673) │ │ │ │ 0166B Filename Length 0013 (19) │ │ │ │ 0166D Extra Length 001C (28) │ │ │ │ 0166F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x166F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 01682 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 01684 Length 0009 (9) │ │ │ │ 01686 Flags 03 (3) 'Modification Access' │ │ │ │ -01687 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -0168B Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +01687 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +0168B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 0168F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 01691 Length 000B (11) │ │ │ │ 01693 Version 01 (1) │ │ │ │ 01694 UID Size 04 (4) │ │ │ │ 01695 UID 00000000 (0) │ │ │ │ 01699 GID Size 04 (4) │ │ │ │ 0169A GID 00000000 (0) │ │ │ │ @@ -93,898 +93,898 @@ │ │ │ │ │ │ │ │ 01D73 LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ 01D77 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 01D78 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 01D79 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 01D7B Compression Method 0008 (8) 'Deflated' │ │ │ │ -01D7D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -01D81 CRC DD59CFB1 (3713650609) │ │ │ │ -01D85 Compressed Size 00002DA9 (11689) │ │ │ │ +01D7D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +01D81 CRC 0E61F7BC (241301436) │ │ │ │ +01D85 Compressed Size 00002D9F (11679) │ │ │ │ 01D89 Uncompressed Size 0000D0BF (53439) │ │ │ │ 01D8D Filename Length 0014 (20) │ │ │ │ 01D8F Extra Length 001C (28) │ │ │ │ 01D91 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x1D91: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 01DA5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 01DA7 Length 0009 (9) │ │ │ │ 01DA9 Flags 03 (3) 'Modification Access' │ │ │ │ -01DAA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -01DAE Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +01DAA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +01DAE Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 01DB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 01DB4 Length 000B (11) │ │ │ │ 01DB6 Version 01 (1) │ │ │ │ 01DB7 UID Size 04 (4) │ │ │ │ 01DB8 UID 00000000 (0) │ │ │ │ 01DBC GID Size 04 (4) │ │ │ │ 01DBD GID 00000000 (0) │ │ │ │ 01DC1 PAYLOAD │ │ │ │ │ │ │ │ -04B6A LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -04B6E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04B6F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04B70 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04B72 Compression Method 0008 (8) 'Deflated' │ │ │ │ -04B74 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -04B78 CRC B8F4A452 (3103040594) │ │ │ │ -04B7C Compressed Size 000003F0 (1008) │ │ │ │ -04B80 Uncompressed Size 00000876 (2166) │ │ │ │ -04B84 Filename Length 0014 (20) │ │ │ │ -04B86 Extra Length 001C (28) │ │ │ │ -04B88 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4B88: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04B9C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04B9E Length 0009 (9) │ │ │ │ -04BA0 Flags 03 (3) 'Modification Access' │ │ │ │ -04BA1 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -04BA5 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -04BA9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04BAB Length 000B (11) │ │ │ │ -04BAD Version 01 (1) │ │ │ │ -04BAE UID Size 04 (4) │ │ │ │ -04BAF UID 00000000 (0) │ │ │ │ -04BB3 GID Size 04 (4) │ │ │ │ -04BB4 GID 00000000 (0) │ │ │ │ -04BB8 PAYLOAD │ │ │ │ - │ │ │ │ -04FA8 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ -04FAC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04FAD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04FAE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04FB0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -04FB2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -04FB6 CRC D32DA388 (3542983560) │ │ │ │ -04FBA Compressed Size 000001AE (430) │ │ │ │ -04FBE Uncompressed Size 000002FC (764) │ │ │ │ -04FC2 Filename Length 0011 (17) │ │ │ │ -04FC4 Extra Length 001C (28) │ │ │ │ -04FC6 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4FC6: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04FD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04FD9 Length 0009 (9) │ │ │ │ -04FDB Flags 03 (3) 'Modification Access' │ │ │ │ -04FDC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -04FE0 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -04FE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04FE6 Length 000B (11) │ │ │ │ -04FE8 Version 01 (1) │ │ │ │ -04FE9 UID Size 04 (4) │ │ │ │ -04FEA UID 00000000 (0) │ │ │ │ -04FEE GID Size 04 (4) │ │ │ │ -04FEF GID 00000000 (0) │ │ │ │ -04FF3 PAYLOAD │ │ │ │ - │ │ │ │ -051A1 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ -051A5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -051A6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -051A7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -051A9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -051AB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -051AF CRC D5BF807E (3586097278) │ │ │ │ -051B3 Compressed Size 000020C6 (8390) │ │ │ │ -051B7 Uncompressed Size 0000B4B0 (46256) │ │ │ │ -051BB Filename Length 001B (27) │ │ │ │ -051BD Extra Length 001C (28) │ │ │ │ -051BF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x51BF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -051DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -051DC Length 0009 (9) │ │ │ │ -051DE Flags 03 (3) 'Modification Access' │ │ │ │ -051DF Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -051E3 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -051E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -051E9 Length 000B (11) │ │ │ │ -051EB Version 01 (1) │ │ │ │ -051EC UID Size 04 (4) │ │ │ │ -051ED UID 00000000 (0) │ │ │ │ -051F1 GID Size 04 (4) │ │ │ │ -051F2 GID 00000000 (0) │ │ │ │ -051F6 PAYLOAD │ │ │ │ - │ │ │ │ -072BC LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ -072C0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -072C1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -072C2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -072C4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -072C6 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -072CA CRC CB35CCED (3409300717) │ │ │ │ -072CE Compressed Size 00000E6F (3695) │ │ │ │ -072D2 Uncompressed Size 000030B2 (12466) │ │ │ │ -072D6 Filename Length 001D (29) │ │ │ │ -072D8 Extra Length 001C (28) │ │ │ │ -072DA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x72DA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -072F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -072F9 Length 0009 (9) │ │ │ │ -072FB Flags 03 (3) 'Modification Access' │ │ │ │ -072FC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -07300 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -07304 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -07306 Length 000B (11) │ │ │ │ -07308 Version 01 (1) │ │ │ │ -07309 UID Size 04 (4) │ │ │ │ -0730A UID 00000000 (0) │ │ │ │ -0730E GID Size 04 (4) │ │ │ │ -0730F GID 00000000 (0) │ │ │ │ -07313 PAYLOAD │ │ │ │ - │ │ │ │ -08182 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -08186 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08187 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08188 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0818A Compression Method 0008 (8) 'Deflated' │ │ │ │ -0818C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -08190 CRC D01F57B3 (3491714995) │ │ │ │ -08194 Compressed Size 00000972 (2418) │ │ │ │ -08198 Uncompressed Size 00001CB2 (7346) │ │ │ │ -0819C Filename Length 0019 (25) │ │ │ │ -0819E Extra Length 001C (28) │ │ │ │ -081A0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81A0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -081B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -081BB Length 0009 (9) │ │ │ │ -081BD Flags 03 (3) 'Modification Access' │ │ │ │ -081BE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -081C2 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -081C6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -081C8 Length 000B (11) │ │ │ │ -081CA Version 01 (1) │ │ │ │ -081CB UID Size 04 (4) │ │ │ │ -081CC UID 00000000 (0) │ │ │ │ -081D0 GID Size 04 (4) │ │ │ │ -081D1 GID 00000000 (0) │ │ │ │ -081D5 PAYLOAD │ │ │ │ - │ │ │ │ -08B47 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08B4B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08B4C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08B4D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08B4F Compression Method 0008 (8) 'Deflated' │ │ │ │ -08B51 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -08B55 CRC 92DCCD7D (2463944061) │ │ │ │ -08B59 Compressed Size 0000387F (14463) │ │ │ │ -08B5D Uncompressed Size 0000F7F4 (63476) │ │ │ │ -08B61 Filename Length 0015 (21) │ │ │ │ -08B63 Extra Length 001C (28) │ │ │ │ -08B65 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8B65: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08B7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08B7C Length 0009 (9) │ │ │ │ -08B7E Flags 03 (3) 'Modification Access' │ │ │ │ -08B7F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -08B83 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -08B87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08B89 Length 000B (11) │ │ │ │ -08B8B Version 01 (1) │ │ │ │ -08B8C UID Size 04 (4) │ │ │ │ -08B8D UID 00000000 (0) │ │ │ │ -08B91 GID Size 04 (4) │ │ │ │ -08B92 GID 00000000 (0) │ │ │ │ -08B96 PAYLOAD │ │ │ │ - │ │ │ │ -0C415 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0C419 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0C41A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0C41B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0C41D Compression Method 0008 (8) 'Deflated' │ │ │ │ -0C41F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -0C423 CRC 2368599F (594041247) │ │ │ │ -0C427 Compressed Size 0000AAD3 (43731) │ │ │ │ -0C42B Uncompressed Size 0003DFDE (253918) │ │ │ │ -0C42F Filename Length 0012 (18) │ │ │ │ -0C431 Extra Length 001C (28) │ │ │ │ -0C433 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC433: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0C445 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0C447 Length 0009 (9) │ │ │ │ -0C449 Flags 03 (3) 'Modification Access' │ │ │ │ -0C44A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -0C44E Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -0C452 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0C454 Length 000B (11) │ │ │ │ -0C456 Version 01 (1) │ │ │ │ -0C457 UID Size 04 (4) │ │ │ │ -0C458 UID 00000000 (0) │ │ │ │ -0C45C GID Size 04 (4) │ │ │ │ -0C45D GID 00000000 (0) │ │ │ │ -0C461 PAYLOAD │ │ │ │ - │ │ │ │ -16F34 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -16F38 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -16F39 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -16F3A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -16F3C Compression Method 0008 (8) 'Deflated' │ │ │ │ -16F3E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -16F42 CRC 2DEC84BF (770475199) │ │ │ │ -16F46 Compressed Size 00003B1E (15134) │ │ │ │ -16F4A Uncompressed Size 0001B2A0 (111264) │ │ │ │ -16F4E Filename Length 0015 (21) │ │ │ │ -16F50 Extra Length 001C (28) │ │ │ │ -16F52 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x16F52: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -16F67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -16F69 Length 0009 (9) │ │ │ │ -16F6B Flags 03 (3) 'Modification Access' │ │ │ │ -16F6C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -16F70 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -16F74 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -16F76 Length 000B (11) │ │ │ │ -16F78 Version 01 (1) │ │ │ │ -16F79 UID Size 04 (4) │ │ │ │ -16F7A UID 00000000 (0) │ │ │ │ -16F7E GID Size 04 (4) │ │ │ │ -16F7F GID 00000000 (0) │ │ │ │ -16F83 PAYLOAD │ │ │ │ +04B60 LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +04B64 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04B65 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04B66 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04B68 Compression Method 0008 (8) 'Deflated' │ │ │ │ +04B6A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +04B6E CRC B8F4A452 (3103040594) │ │ │ │ +04B72 Compressed Size 000003F0 (1008) │ │ │ │ +04B76 Uncompressed Size 00000876 (2166) │ │ │ │ +04B7A Filename Length 0014 (20) │ │ │ │ +04B7C Extra Length 001C (28) │ │ │ │ +04B7E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4B7E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04B92 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04B94 Length 0009 (9) │ │ │ │ +04B96 Flags 03 (3) 'Modification Access' │ │ │ │ +04B97 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +04B9B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +04B9F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04BA1 Length 000B (11) │ │ │ │ +04BA3 Version 01 (1) │ │ │ │ +04BA4 UID Size 04 (4) │ │ │ │ +04BA5 UID 00000000 (0) │ │ │ │ +04BA9 GID Size 04 (4) │ │ │ │ +04BAA GID 00000000 (0) │ │ │ │ +04BAE PAYLOAD │ │ │ │ + │ │ │ │ +04F9E LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ +04FA2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04FA3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04FA4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04FA6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +04FA8 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +04FAC CRC D32DA388 (3542983560) │ │ │ │ +04FB0 Compressed Size 000001AE (430) │ │ │ │ +04FB4 Uncompressed Size 000002FC (764) │ │ │ │ +04FB8 Filename Length 0011 (17) │ │ │ │ +04FBA Extra Length 001C (28) │ │ │ │ +04FBC Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4FBC: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04FCD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04FCF Length 0009 (9) │ │ │ │ +04FD1 Flags 03 (3) 'Modification Access' │ │ │ │ +04FD2 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +04FD6 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +04FDA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04FDC Length 000B (11) │ │ │ │ +04FDE Version 01 (1) │ │ │ │ +04FDF UID Size 04 (4) │ │ │ │ +04FE0 UID 00000000 (0) │ │ │ │ +04FE4 GID Size 04 (4) │ │ │ │ +04FE5 GID 00000000 (0) │ │ │ │ +04FE9 PAYLOAD │ │ │ │ + │ │ │ │ +05197 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ +0519B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0519C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0519D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0519F Compression Method 0008 (8) 'Deflated' │ │ │ │ +051A1 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +051A5 CRC 216D3405 (560804869) │ │ │ │ +051A9 Compressed Size 000020C6 (8390) │ │ │ │ +051AD Uncompressed Size 0000B4B0 (46256) │ │ │ │ +051B1 Filename Length 001B (27) │ │ │ │ +051B3 Extra Length 001C (28) │ │ │ │ +051B5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x51B5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +051D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +051D2 Length 0009 (9) │ │ │ │ +051D4 Flags 03 (3) 'Modification Access' │ │ │ │ +051D5 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +051D9 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +051DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +051DF Length 000B (11) │ │ │ │ +051E1 Version 01 (1) │ │ │ │ +051E2 UID Size 04 (4) │ │ │ │ +051E3 UID 00000000 (0) │ │ │ │ +051E7 GID Size 04 (4) │ │ │ │ +051E8 GID 00000000 (0) │ │ │ │ +051EC PAYLOAD │ │ │ │ + │ │ │ │ +072B2 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ +072B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +072B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +072B8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +072BA Compression Method 0008 (8) 'Deflated' │ │ │ │ +072BC Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +072C0 CRC CB35CCED (3409300717) │ │ │ │ +072C4 Compressed Size 00000E6F (3695) │ │ │ │ +072C8 Uncompressed Size 000030B2 (12466) │ │ │ │ +072CC Filename Length 001D (29) │ │ │ │ +072CE Extra Length 001C (28) │ │ │ │ +072D0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x72D0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +072ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +072EF Length 0009 (9) │ │ │ │ +072F1 Flags 03 (3) 'Modification Access' │ │ │ │ +072F2 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +072F6 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +072FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +072FC Length 000B (11) │ │ │ │ +072FE Version 01 (1) │ │ │ │ +072FF UID Size 04 (4) │ │ │ │ +07300 UID 00000000 (0) │ │ │ │ +07304 GID Size 04 (4) │ │ │ │ +07305 GID 00000000 (0) │ │ │ │ +07309 PAYLOAD │ │ │ │ + │ │ │ │ +08178 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ +0817C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0817D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0817E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08180 Compression Method 0008 (8) 'Deflated' │ │ │ │ +08182 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +08186 CRC D01F57B3 (3491714995) │ │ │ │ +0818A Compressed Size 00000972 (2418) │ │ │ │ +0818E Uncompressed Size 00001CB2 (7346) │ │ │ │ +08192 Filename Length 0019 (25) │ │ │ │ +08194 Extra Length 001C (28) │ │ │ │ +08196 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8196: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +081AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +081B1 Length 0009 (9) │ │ │ │ +081B3 Flags 03 (3) 'Modification Access' │ │ │ │ +081B4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +081B8 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +081BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +081BE Length 000B (11) │ │ │ │ +081C0 Version 01 (1) │ │ │ │ +081C1 UID Size 04 (4) │ │ │ │ +081C2 UID 00000000 (0) │ │ │ │ +081C6 GID Size 04 (4) │ │ │ │ +081C7 GID 00000000 (0) │ │ │ │ +081CB PAYLOAD │ │ │ │ + │ │ │ │ +08B3D LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08B41 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08B42 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08B43 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08B45 Compression Method 0008 (8) 'Deflated' │ │ │ │ +08B47 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +08B4B CRC 274AFEE1 (659226337) │ │ │ │ +08B4F Compressed Size 0000387E (14462) │ │ │ │ +08B53 Uncompressed Size 0000F7F4 (63476) │ │ │ │ +08B57 Filename Length 0015 (21) │ │ │ │ +08B59 Extra Length 001C (28) │ │ │ │ +08B5B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8B5B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08B70 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08B72 Length 0009 (9) │ │ │ │ +08B74 Flags 03 (3) 'Modification Access' │ │ │ │ +08B75 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +08B79 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +08B7D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08B7F Length 000B (11) │ │ │ │ +08B81 Version 01 (1) │ │ │ │ +08B82 UID Size 04 (4) │ │ │ │ +08B83 UID 00000000 (0) │ │ │ │ +08B87 GID Size 04 (4) │ │ │ │ +08B88 GID 00000000 (0) │ │ │ │ +08B8C PAYLOAD │ │ │ │ + │ │ │ │ +0C40A LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +0C40E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0C40F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0C410 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0C412 Compression Method 0008 (8) 'Deflated' │ │ │ │ +0C414 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +0C418 CRC 45EF58BC (1173313724) │ │ │ │ +0C41C Compressed Size 0000AADC (43740) │ │ │ │ +0C420 Uncompressed Size 0003DFDE (253918) │ │ │ │ +0C424 Filename Length 0012 (18) │ │ │ │ +0C426 Extra Length 001C (28) │ │ │ │ +0C428 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC428: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0C43A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0C43C Length 0009 (9) │ │ │ │ +0C43E Flags 03 (3) 'Modification Access' │ │ │ │ +0C43F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +0C443 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +0C447 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0C449 Length 000B (11) │ │ │ │ +0C44B Version 01 (1) │ │ │ │ +0C44C UID Size 04 (4) │ │ │ │ +0C44D UID 00000000 (0) │ │ │ │ +0C451 GID Size 04 (4) │ │ │ │ +0C452 GID 00000000 (0) │ │ │ │ +0C456 PAYLOAD │ │ │ │ + │ │ │ │ +16F32 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +16F36 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +16F37 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +16F38 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +16F3A Compression Method 0008 (8) 'Deflated' │ │ │ │ +16F3C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +16F40 CRC 3A18F4DB (974714075) │ │ │ │ +16F44 Compressed Size 00003B20 (15136) │ │ │ │ +16F48 Uncompressed Size 0001B2A0 (111264) │ │ │ │ +16F4C Filename Length 0015 (21) │ │ │ │ +16F4E Extra Length 001C (28) │ │ │ │ +16F50 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x16F50: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +16F65 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +16F67 Length 0009 (9) │ │ │ │ +16F69 Flags 03 (3) 'Modification Access' │ │ │ │ +16F6A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +16F6E Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +16F72 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +16F74 Length 000B (11) │ │ │ │ +16F76 Version 01 (1) │ │ │ │ +16F77 UID Size 04 (4) │ │ │ │ +16F78 UID 00000000 (0) │ │ │ │ +16F7C GID Size 04 (4) │ │ │ │ +16F7D GID 00000000 (0) │ │ │ │ +16F81 PAYLOAD │ │ │ │ │ │ │ │ 1AAA1 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ 1AAA5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 1AAA6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 1AAA7 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 1AAA9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1AAAB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -1AAAF CRC 352F19D3 (892279251) │ │ │ │ -1AAB3 Compressed Size 00009090 (37008) │ │ │ │ +1AAAB Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +1AAAF CRC 76B59E6D (1991614061) │ │ │ │ +1AAB3 Compressed Size 00009087 (36999) │ │ │ │ 1AAB7 Uncompressed Size 0003D05F (249951) │ │ │ │ 1AABB Filename Length 0014 (20) │ │ │ │ 1AABD Extra Length 001C (28) │ │ │ │ 1AABF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x1AABF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 1AAD3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 1AAD5 Length 0009 (9) │ │ │ │ 1AAD7 Flags 03 (3) 'Modification Access' │ │ │ │ -1AAD8 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -1AADC Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +1AAD8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +1AADC Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 1AAE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 1AAE2 Length 000B (11) │ │ │ │ 1AAE4 Version 01 (1) │ │ │ │ 1AAE5 UID Size 04 (4) │ │ │ │ 1AAE6 UID 00000000 (0) │ │ │ │ 1AAEA GID Size 04 (4) │ │ │ │ 1AAEB GID 00000000 (0) │ │ │ │ 1AAEF PAYLOAD │ │ │ │ │ │ │ │ -23B7F LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -23B83 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -23B84 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -23B85 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -23B87 Compression Method 0008 (8) 'Deflated' │ │ │ │ -23B89 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -23B8D CRC A14752A3 (2705805987) │ │ │ │ -23B91 Compressed Size 00002A65 (10853) │ │ │ │ -23B95 Uncompressed Size 0001151F (70943) │ │ │ │ -23B99 Filename Length 0016 (22) │ │ │ │ -23B9B Extra Length 001C (28) │ │ │ │ -23B9D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x23B9D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -23BB3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -23BB5 Length 0009 (9) │ │ │ │ -23BB7 Flags 03 (3) 'Modification Access' │ │ │ │ -23BB8 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -23BBC Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -23BC0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -23BC2 Length 000B (11) │ │ │ │ -23BC4 Version 01 (1) │ │ │ │ -23BC5 UID Size 04 (4) │ │ │ │ -23BC6 UID 00000000 (0) │ │ │ │ -23BCA GID Size 04 (4) │ │ │ │ -23BCB GID 00000000 (0) │ │ │ │ -23BCF PAYLOAD │ │ │ │ - │ │ │ │ -26634 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -26638 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -26639 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2663A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2663C Compression Method 0008 (8) 'Deflated' │ │ │ │ -2663E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -26642 CRC F1E18143 (4058087747) │ │ │ │ -26646 Compressed Size 000014D8 (5336) │ │ │ │ -2664A Uncompressed Size 00005176 (20854) │ │ │ │ -2664E Filename Length 001D (29) │ │ │ │ -26650 Extra Length 001C (28) │ │ │ │ -26652 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x26652: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2666F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -26671 Length 0009 (9) │ │ │ │ -26673 Flags 03 (3) 'Modification Access' │ │ │ │ -26674 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -26678 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2667C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2667E Length 000B (11) │ │ │ │ -26680 Version 01 (1) │ │ │ │ -26681 UID Size 04 (4) │ │ │ │ -26682 UID 00000000 (0) │ │ │ │ -26686 GID Size 04 (4) │ │ │ │ -26687 GID 00000000 (0) │ │ │ │ -2668B PAYLOAD │ │ │ │ - │ │ │ │ -27B63 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -27B67 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -27B68 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -27B69 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -27B6B Compression Method 0008 (8) 'Deflated' │ │ │ │ -27B6D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -27B71 CRC 78898277 (2022277751) │ │ │ │ -27B75 Compressed Size 000037F6 (14326) │ │ │ │ -27B79 Uncompressed Size 0000E9F0 (59888) │ │ │ │ -27B7D Filename Length 001C (28) │ │ │ │ -27B7F Extra Length 001C (28) │ │ │ │ -27B81 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x27B81: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -27B9D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -27B9F Length 0009 (9) │ │ │ │ -27BA1 Flags 03 (3) 'Modification Access' │ │ │ │ -27BA2 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -27BA6 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -27BAA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -27BAC Length 000B (11) │ │ │ │ -27BAE Version 01 (1) │ │ │ │ -27BAF UID Size 04 (4) │ │ │ │ -27BB0 UID 00000000 (0) │ │ │ │ -27BB4 GID Size 04 (4) │ │ │ │ -27BB5 GID 00000000 (0) │ │ │ │ -27BB9 PAYLOAD │ │ │ │ - │ │ │ │ -2B3AF LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -2B3B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2B3B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2B3B5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2B3B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2B3B9 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -2B3BD CRC 6A63C5E7 (1784923623) │ │ │ │ -2B3C1 Compressed Size 000006A0 (1696) │ │ │ │ -2B3C5 Uncompressed Size 000011F4 (4596) │ │ │ │ -2B3C9 Filename Length 001C (28) │ │ │ │ -2B3CB Extra Length 001C (28) │ │ │ │ -2B3CD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B3CD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2B3E9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2B3EB Length 0009 (9) │ │ │ │ -2B3ED Flags 03 (3) 'Modification Access' │ │ │ │ -2B3EE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2B3F2 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2B3F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2B3F8 Length 000B (11) │ │ │ │ -2B3FA Version 01 (1) │ │ │ │ -2B3FB UID Size 04 (4) │ │ │ │ -2B3FC UID 00000000 (0) │ │ │ │ -2B400 GID Size 04 (4) │ │ │ │ -2B401 GID 00000000 (0) │ │ │ │ -2B405 PAYLOAD │ │ │ │ - │ │ │ │ -2BAA5 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ -2BAA9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2BAAA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2BAAB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2BAAD Compression Method 0008 (8) 'Deflated' │ │ │ │ -2BAAF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -2BAB3 CRC 4B420ADA (1262619354) │ │ │ │ -2BAB7 Compressed Size 0000107B (4219) │ │ │ │ -2BABB Uncompressed Size 00004BFF (19455) │ │ │ │ -2BABF Filename Length 001B (27) │ │ │ │ -2BAC1 Extra Length 001C (28) │ │ │ │ -2BAC3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2BAC3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2BADE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2BAE0 Length 0009 (9) │ │ │ │ -2BAE2 Flags 03 (3) 'Modification Access' │ │ │ │ -2BAE3 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2BAE7 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2BAEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2BAED Length 000B (11) │ │ │ │ -2BAEF Version 01 (1) │ │ │ │ -2BAF0 UID Size 04 (4) │ │ │ │ -2BAF1 UID 00000000 (0) │ │ │ │ -2BAF5 GID Size 04 (4) │ │ │ │ -2BAF6 GID 00000000 (0) │ │ │ │ -2BAFA PAYLOAD │ │ │ │ - │ │ │ │ -2CB75 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -2CB79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2CB7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2CB7B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2CB7D Compression Method 0008 (8) 'Deflated' │ │ │ │ -2CB7F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -2CB83 CRC A579921F (2776207903) │ │ │ │ -2CB87 Compressed Size 000033AB (13227) │ │ │ │ -2CB8B Uncompressed Size 0000BC94 (48276) │ │ │ │ -2CB8F Filename Length 001D (29) │ │ │ │ -2CB91 Extra Length 001C (28) │ │ │ │ -2CB93 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2CB93: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2CBB0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2CBB2 Length 0009 (9) │ │ │ │ -2CBB4 Flags 03 (3) 'Modification Access' │ │ │ │ -2CBB5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2CBB9 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2CBBD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2CBBF Length 000B (11) │ │ │ │ -2CBC1 Version 01 (1) │ │ │ │ -2CBC2 UID Size 04 (4) │ │ │ │ -2CBC3 UID 00000000 (0) │ │ │ │ -2CBC7 GID Size 04 (4) │ │ │ │ -2CBC8 GID 00000000 (0) │ │ │ │ -2CBCC PAYLOAD │ │ │ │ - │ │ │ │ -2FF77 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -2FF7B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2FF7C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2FF7D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2FF7F Compression Method 0008 (8) 'Deflated' │ │ │ │ -2FF81 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -2FF85 CRC 3BBA297E (1002056062) │ │ │ │ -2FF89 Compressed Size 00000D6C (3436) │ │ │ │ -2FF8D Uncompressed Size 00003876 (14454) │ │ │ │ -2FF91 Filename Length 001D (29) │ │ │ │ -2FF93 Extra Length 001C (28) │ │ │ │ -2FF95 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2FF95: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2FFB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2FFB4 Length 0009 (9) │ │ │ │ -2FFB6 Flags 03 (3) 'Modification Access' │ │ │ │ -2FFB7 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2FFBB Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -2FFBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2FFC1 Length 000B (11) │ │ │ │ -2FFC3 Version 01 (1) │ │ │ │ -2FFC4 UID Size 04 (4) │ │ │ │ -2FFC5 UID 00000000 (0) │ │ │ │ -2FFC9 GID Size 04 (4) │ │ │ │ -2FFCA GID 00000000 (0) │ │ │ │ -2FFCE PAYLOAD │ │ │ │ - │ │ │ │ -30D3A LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -30D3E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -30D3F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -30D40 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -30D42 Compression Method 0008 (8) 'Deflated' │ │ │ │ -30D44 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -30D48 CRC CE4502E0 (3460629216) │ │ │ │ -30D4C Compressed Size 00001C6A (7274) │ │ │ │ -30D50 Uncompressed Size 0000C186 (49542) │ │ │ │ -30D54 Filename Length 001A (26) │ │ │ │ -30D56 Extra Length 001C (28) │ │ │ │ -30D58 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x30D58: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -30D72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -30D74 Length 0009 (9) │ │ │ │ -30D76 Flags 03 (3) 'Modification Access' │ │ │ │ -30D77 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -30D7B Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -30D7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -30D81 Length 000B (11) │ │ │ │ -30D83 Version 01 (1) │ │ │ │ -30D84 UID Size 04 (4) │ │ │ │ -30D85 UID 00000000 (0) │ │ │ │ -30D89 GID Size 04 (4) │ │ │ │ -30D8A GID 00000000 (0) │ │ │ │ -30D8E PAYLOAD │ │ │ │ - │ │ │ │ -329F8 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -329FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -329FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -329FE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32A00 Compression Method 0008 (8) 'Deflated' │ │ │ │ -32A02 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -32A06 CRC 65574916 (1700219158) │ │ │ │ -32A0A Compressed Size 000003A3 (931) │ │ │ │ -32A0E Uncompressed Size 0000088E (2190) │ │ │ │ -32A12 Filename Length 0012 (18) │ │ │ │ -32A14 Extra Length 001C (28) │ │ │ │ -32A16 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32A16: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32A28 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32A2A Length 0009 (9) │ │ │ │ -32A2C Flags 03 (3) 'Modification Access' │ │ │ │ -32A2D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -32A31 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -32A35 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32A37 Length 000B (11) │ │ │ │ -32A39 Version 01 (1) │ │ │ │ -32A3A UID Size 04 (4) │ │ │ │ -32A3B UID 00000000 (0) │ │ │ │ -32A3F GID Size 04 (4) │ │ │ │ -32A40 GID 00000000 (0) │ │ │ │ -32A44 PAYLOAD │ │ │ │ - │ │ │ │ -32DE7 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -32DEB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -32DEC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -32DED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32DEF Compression Method 0008 (8) 'Deflated' │ │ │ │ -32DF1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -32DF5 CRC 83472134 (2202476852) │ │ │ │ -32DF9 Compressed Size 000001D4 (468) │ │ │ │ -32DFD Uncompressed Size 00000311 (785) │ │ │ │ -32E01 Filename Length 0020 (32) │ │ │ │ -32E03 Extra Length 001C (28) │ │ │ │ -32E05 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32E05: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32E25 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32E27 Length 0009 (9) │ │ │ │ -32E29 Flags 03 (3) 'Modification Access' │ │ │ │ -32E2A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -32E2E Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -32E32 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32E34 Length 000B (11) │ │ │ │ -32E36 Version 01 (1) │ │ │ │ -32E37 UID Size 04 (4) │ │ │ │ -32E38 UID 00000000 (0) │ │ │ │ -32E3C GID Size 04 (4) │ │ │ │ -32E3D GID 00000000 (0) │ │ │ │ -32E41 PAYLOAD │ │ │ │ - │ │ │ │ -33015 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -33019 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3301A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3301B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3301D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3301F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -33023 CRC E5289043 (3844640835) │ │ │ │ -33027 Compressed Size 000017A7 (6055) │ │ │ │ -3302B Uncompressed Size 00009CD3 (40147) │ │ │ │ -3302F Filename Length 001B (27) │ │ │ │ -33031 Extra Length 001C (28) │ │ │ │ -33033 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33033: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3304E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33050 Length 0009 (9) │ │ │ │ -33052 Flags 03 (3) 'Modification Access' │ │ │ │ -33053 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -33057 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3305B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3305D Length 000B (11) │ │ │ │ -3305F Version 01 (1) │ │ │ │ -33060 UID Size 04 (4) │ │ │ │ -33061 UID 00000000 (0) │ │ │ │ -33065 GID Size 04 (4) │ │ │ │ -33066 GID 00000000 (0) │ │ │ │ -3306A PAYLOAD │ │ │ │ - │ │ │ │ -34811 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -34815 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -34816 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -34817 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -34819 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3481B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3481F CRC 5F73B5AE (1601418670) │ │ │ │ -34823 Compressed Size 00001371 (4977) │ │ │ │ -34827 Uncompressed Size 00003B66 (15206) │ │ │ │ -3482B Filename Length 0015 (21) │ │ │ │ -3482D Extra Length 001C (28) │ │ │ │ -3482F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3482F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -34844 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -34846 Length 0009 (9) │ │ │ │ -34848 Flags 03 (3) 'Modification Access' │ │ │ │ -34849 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3484D Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -34851 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -34853 Length 000B (11) │ │ │ │ -34855 Version 01 (1) │ │ │ │ -34856 UID Size 04 (4) │ │ │ │ -34857 UID 00000000 (0) │ │ │ │ -3485B GID Size 04 (4) │ │ │ │ -3485C GID 00000000 (0) │ │ │ │ -34860 PAYLOAD │ │ │ │ - │ │ │ │ -35BD1 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -35BD5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35BD6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35BD7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35BD9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -35BDB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -35BDF CRC 7FAE09B6 (2142112182) │ │ │ │ -35BE3 Compressed Size 00000AD1 (2769) │ │ │ │ -35BE7 Uncompressed Size 00002135 (8501) │ │ │ │ -35BEB Filename Length 0011 (17) │ │ │ │ -35BED Extra Length 001C (28) │ │ │ │ -35BEF Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35BEF: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35C00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35C02 Length 0009 (9) │ │ │ │ -35C04 Flags 03 (3) 'Modification Access' │ │ │ │ -35C05 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -35C09 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -35C0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35C0F Length 000B (11) │ │ │ │ -35C11 Version 01 (1) │ │ │ │ -35C12 UID Size 04 (4) │ │ │ │ -35C13 UID 00000000 (0) │ │ │ │ -35C17 GID Size 04 (4) │ │ │ │ -35C18 GID 00000000 (0) │ │ │ │ -35C1C PAYLOAD │ │ │ │ - │ │ │ │ -366ED LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -366F1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -366F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -366F3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -366F5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -366F7 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -366FB CRC F2F690A2 (4076245154) │ │ │ │ -366FF Compressed Size 000003FE (1022) │ │ │ │ -36703 Uncompressed Size 00000E99 (3737) │ │ │ │ -36707 Filename Length 0014 (20) │ │ │ │ -36709 Extra Length 001C (28) │ │ │ │ -3670B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3670B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3671F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -36721 Length 0009 (9) │ │ │ │ -36723 Flags 03 (3) 'Modification Access' │ │ │ │ -36724 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -36728 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3672C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3672E Length 000B (11) │ │ │ │ -36730 Version 01 (1) │ │ │ │ -36731 UID Size 04 (4) │ │ │ │ -36732 UID 00000000 (0) │ │ │ │ -36736 GID Size 04 (4) │ │ │ │ -36737 GID 00000000 (0) │ │ │ │ -3673B PAYLOAD │ │ │ │ - │ │ │ │ -36B39 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -36B3D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -36B3E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -36B3F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -36B41 Compression Method 0008 (8) 'Deflated' │ │ │ │ -36B43 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -36B47 CRC EB83ACB7 (3951275191) │ │ │ │ -36B4B Compressed Size 00001261 (4705) │ │ │ │ -36B4F Uncompressed Size 00003469 (13417) │ │ │ │ -36B53 Filename Length 0014 (20) │ │ │ │ -36B55 Extra Length 001C (28) │ │ │ │ -36B57 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x36B57: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -36B6B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -36B6D Length 0009 (9) │ │ │ │ -36B6F Flags 03 (3) 'Modification Access' │ │ │ │ -36B70 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -36B74 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -36B78 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -36B7A Length 000B (11) │ │ │ │ -36B7C Version 01 (1) │ │ │ │ -36B7D UID Size 04 (4) │ │ │ │ -36B7E UID 00000000 (0) │ │ │ │ -36B82 GID Size 04 (4) │ │ │ │ -36B83 GID 00000000 (0) │ │ │ │ -36B87 PAYLOAD │ │ │ │ - │ │ │ │ -37DE8 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -37DEC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -37DED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -37DEE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -37DF0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -37DF2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -37DF6 CRC ED6E01D4 (3983409620) │ │ │ │ -37DFA Compressed Size 00000AD0 (2768) │ │ │ │ -37DFE Uncompressed Size 000022FF (8959) │ │ │ │ -37E02 Filename Length 001B (27) │ │ │ │ -37E04 Extra Length 001C (28) │ │ │ │ -37E06 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x37E06: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -37E21 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -37E23 Length 0009 (9) │ │ │ │ -37E25 Flags 03 (3) 'Modification Access' │ │ │ │ -37E26 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -37E2A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -37E2E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -37E30 Length 000B (11) │ │ │ │ -37E32 Version 01 (1) │ │ │ │ -37E33 UID Size 04 (4) │ │ │ │ -37E34 UID 00000000 (0) │ │ │ │ -37E38 GID Size 04 (4) │ │ │ │ -37E39 GID 00000000 (0) │ │ │ │ -37E3D PAYLOAD │ │ │ │ +23B76 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +23B7A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +23B7B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +23B7C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +23B7E Compression Method 0008 (8) 'Deflated' │ │ │ │ +23B80 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +23B84 CRC E1D62D32 (3788909874) │ │ │ │ +23B88 Compressed Size 00002A68 (10856) │ │ │ │ +23B8C Uncompressed Size 0001151F (70943) │ │ │ │ +23B90 Filename Length 0016 (22) │ │ │ │ +23B92 Extra Length 001C (28) │ │ │ │ +23B94 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x23B94: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +23BAA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +23BAC Length 0009 (9) │ │ │ │ +23BAE Flags 03 (3) 'Modification Access' │ │ │ │ +23BAF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +23BB3 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +23BB7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +23BB9 Length 000B (11) │ │ │ │ +23BBB Version 01 (1) │ │ │ │ +23BBC UID Size 04 (4) │ │ │ │ +23BBD UID 00000000 (0) │ │ │ │ +23BC1 GID Size 04 (4) │ │ │ │ +23BC2 GID 00000000 (0) │ │ │ │ +23BC6 PAYLOAD │ │ │ │ + │ │ │ │ +2662E LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +26632 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +26633 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +26634 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +26636 Compression Method 0008 (8) 'Deflated' │ │ │ │ +26638 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +2663C CRC 99693BA1 (2573810593) │ │ │ │ +26640 Compressed Size 000014D8 (5336) │ │ │ │ +26644 Uncompressed Size 00005176 (20854) │ │ │ │ +26648 Filename Length 001D (29) │ │ │ │ +2664A Extra Length 001C (28) │ │ │ │ +2664C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2664C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +26669 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2666B Length 0009 (9) │ │ │ │ +2666D Flags 03 (3) 'Modification Access' │ │ │ │ +2666E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +26672 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +26676 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +26678 Length 000B (11) │ │ │ │ +2667A Version 01 (1) │ │ │ │ +2667B UID Size 04 (4) │ │ │ │ +2667C UID 00000000 (0) │ │ │ │ +26680 GID Size 04 (4) │ │ │ │ +26681 GID 00000000 (0) │ │ │ │ +26685 PAYLOAD │ │ │ │ + │ │ │ │ +27B5D LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +27B61 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +27B62 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +27B63 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +27B65 Compression Method 0008 (8) 'Deflated' │ │ │ │ +27B67 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +27B6B CRC F7498B6D (4148792173) │ │ │ │ +27B6F Compressed Size 000037F9 (14329) │ │ │ │ +27B73 Uncompressed Size 0000E9F0 (59888) │ │ │ │ +27B77 Filename Length 001C (28) │ │ │ │ +27B79 Extra Length 001C (28) │ │ │ │ +27B7B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x27B7B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +27B97 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +27B99 Length 0009 (9) │ │ │ │ +27B9B Flags 03 (3) 'Modification Access' │ │ │ │ +27B9C Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +27BA0 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +27BA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +27BA6 Length 000B (11) │ │ │ │ +27BA8 Version 01 (1) │ │ │ │ +27BA9 UID Size 04 (4) │ │ │ │ +27BAA UID 00000000 (0) │ │ │ │ +27BAE GID Size 04 (4) │ │ │ │ +27BAF GID 00000000 (0) │ │ │ │ +27BB3 PAYLOAD │ │ │ │ + │ │ │ │ +2B3AC LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +2B3B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2B3B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2B3B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2B3B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2B3B6 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +2B3BA CRC 6A63C5E7 (1784923623) │ │ │ │ +2B3BE Compressed Size 000006A0 (1696) │ │ │ │ +2B3C2 Uncompressed Size 000011F4 (4596) │ │ │ │ +2B3C6 Filename Length 001C (28) │ │ │ │ +2B3C8 Extra Length 001C (28) │ │ │ │ +2B3CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B3CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2B3E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2B3E8 Length 0009 (9) │ │ │ │ +2B3EA Flags 03 (3) 'Modification Access' │ │ │ │ +2B3EB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2B3EF Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2B3F3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2B3F5 Length 000B (11) │ │ │ │ +2B3F7 Version 01 (1) │ │ │ │ +2B3F8 UID Size 04 (4) │ │ │ │ +2B3F9 UID 00000000 (0) │ │ │ │ +2B3FD GID Size 04 (4) │ │ │ │ +2B3FE GID 00000000 (0) │ │ │ │ +2B402 PAYLOAD │ │ │ │ + │ │ │ │ +2BAA2 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ +2BAA6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2BAA7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2BAA8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2BAAA Compression Method 0008 (8) 'Deflated' │ │ │ │ +2BAAC Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +2BAB0 CRC A55B953B (2774242619) │ │ │ │ +2BAB4 Compressed Size 0000107D (4221) │ │ │ │ +2BAB8 Uncompressed Size 00004BFF (19455) │ │ │ │ +2BABC Filename Length 001B (27) │ │ │ │ +2BABE Extra Length 001C (28) │ │ │ │ +2BAC0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2BAC0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2BADB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2BADD Length 0009 (9) │ │ │ │ +2BADF Flags 03 (3) 'Modification Access' │ │ │ │ +2BAE0 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2BAE4 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2BAE8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2BAEA Length 000B (11) │ │ │ │ +2BAEC Version 01 (1) │ │ │ │ +2BAED UID Size 04 (4) │ │ │ │ +2BAEE UID 00000000 (0) │ │ │ │ +2BAF2 GID Size 04 (4) │ │ │ │ +2BAF3 GID 00000000 (0) │ │ │ │ +2BAF7 PAYLOAD │ │ │ │ + │ │ │ │ +2CB74 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +2CB78 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2CB79 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2CB7A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2CB7C Compression Method 0008 (8) 'Deflated' │ │ │ │ +2CB7E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +2CB82 CRC 424992B3 (1112117939) │ │ │ │ +2CB86 Compressed Size 000033AA (13226) │ │ │ │ +2CB8A Uncompressed Size 0000BC94 (48276) │ │ │ │ +2CB8E Filename Length 001D (29) │ │ │ │ +2CB90 Extra Length 001C (28) │ │ │ │ +2CB92 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2CB92: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2CBAF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2CBB1 Length 0009 (9) │ │ │ │ +2CBB3 Flags 03 (3) 'Modification Access' │ │ │ │ +2CBB4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2CBB8 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2CBBC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2CBBE Length 000B (11) │ │ │ │ +2CBC0 Version 01 (1) │ │ │ │ +2CBC1 UID Size 04 (4) │ │ │ │ +2CBC2 UID 00000000 (0) │ │ │ │ +2CBC6 GID Size 04 (4) │ │ │ │ +2CBC7 GID 00000000 (0) │ │ │ │ +2CBCB PAYLOAD │ │ │ │ + │ │ │ │ +2FF75 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +2FF79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2FF7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2FF7B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2FF7D Compression Method 0008 (8) 'Deflated' │ │ │ │ +2FF7F Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +2FF83 CRC B5695653 (3043579475) │ │ │ │ +2FF87 Compressed Size 00000D6B (3435) │ │ │ │ +2FF8B Uncompressed Size 00003876 (14454) │ │ │ │ +2FF8F Filename Length 001D (29) │ │ │ │ +2FF91 Extra Length 001C (28) │ │ │ │ +2FF93 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2FF93: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2FFB0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2FFB2 Length 0009 (9) │ │ │ │ +2FFB4 Flags 03 (3) 'Modification Access' │ │ │ │ +2FFB5 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2FFB9 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +2FFBD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2FFBF Length 000B (11) │ │ │ │ +2FFC1 Version 01 (1) │ │ │ │ +2FFC2 UID Size 04 (4) │ │ │ │ +2FFC3 UID 00000000 (0) │ │ │ │ +2FFC7 GID Size 04 (4) │ │ │ │ +2FFC8 GID 00000000 (0) │ │ │ │ +2FFCC PAYLOAD │ │ │ │ + │ │ │ │ +30D37 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +30D3B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +30D3C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +30D3D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +30D3F Compression Method 0008 (8) 'Deflated' │ │ │ │ +30D41 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +30D45 CRC 07D12257 (131146327) │ │ │ │ +30D49 Compressed Size 00001C6C (7276) │ │ │ │ +30D4D Uncompressed Size 0000C186 (49542) │ │ │ │ +30D51 Filename Length 001A (26) │ │ │ │ +30D53 Extra Length 001C (28) │ │ │ │ +30D55 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x30D55: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +30D6F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +30D71 Length 0009 (9) │ │ │ │ +30D73 Flags 03 (3) 'Modification Access' │ │ │ │ +30D74 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +30D78 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +30D7C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +30D7E Length 000B (11) │ │ │ │ +30D80 Version 01 (1) │ │ │ │ +30D81 UID Size 04 (4) │ │ │ │ +30D82 UID 00000000 (0) │ │ │ │ +30D86 GID Size 04 (4) │ │ │ │ +30D87 GID 00000000 (0) │ │ │ │ +30D8B PAYLOAD │ │ │ │ + │ │ │ │ +329F7 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +329FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +329FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +329FD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +329FF Compression Method 0008 (8) 'Deflated' │ │ │ │ +32A01 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +32A05 CRC 65574916 (1700219158) │ │ │ │ +32A09 Compressed Size 000003A3 (931) │ │ │ │ +32A0D Uncompressed Size 0000088E (2190) │ │ │ │ +32A11 Filename Length 0012 (18) │ │ │ │ +32A13 Extra Length 001C (28) │ │ │ │ +32A15 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32A15: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +32A27 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +32A29 Length 0009 (9) │ │ │ │ +32A2B Flags 03 (3) 'Modification Access' │ │ │ │ +32A2C Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +32A30 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +32A34 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32A36 Length 000B (11) │ │ │ │ +32A38 Version 01 (1) │ │ │ │ +32A39 UID Size 04 (4) │ │ │ │ +32A3A UID 00000000 (0) │ │ │ │ +32A3E GID Size 04 (4) │ │ │ │ +32A3F GID 00000000 (0) │ │ │ │ +32A43 PAYLOAD │ │ │ │ + │ │ │ │ +32DE6 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +32DEA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32DEB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32DEC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +32DEE Compression Method 0008 (8) 'Deflated' │ │ │ │ +32DF0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +32DF4 CRC 83472134 (2202476852) │ │ │ │ +32DF8 Compressed Size 000001D4 (468) │ │ │ │ +32DFC Uncompressed Size 00000311 (785) │ │ │ │ +32E00 Filename Length 0020 (32) │ │ │ │ +32E02 Extra Length 001C (28) │ │ │ │ +32E04 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32E04: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +32E24 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +32E26 Length 0009 (9) │ │ │ │ +32E28 Flags 03 (3) 'Modification Access' │ │ │ │ +32E29 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +32E2D Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +32E31 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32E33 Length 000B (11) │ │ │ │ +32E35 Version 01 (1) │ │ │ │ +32E36 UID Size 04 (4) │ │ │ │ +32E37 UID 00000000 (0) │ │ │ │ +32E3B GID Size 04 (4) │ │ │ │ +32E3C GID 00000000 (0) │ │ │ │ +32E40 PAYLOAD │ │ │ │ + │ │ │ │ +33014 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +33018 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +33019 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3301A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3301C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3301E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +33022 CRC 5EB37717 (1588819735) │ │ │ │ +33026 Compressed Size 000017AA (6058) │ │ │ │ +3302A Uncompressed Size 00009CD3 (40147) │ │ │ │ +3302E Filename Length 001B (27) │ │ │ │ +33030 Extra Length 001C (28) │ │ │ │ +33032 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x33032: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3304D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3304F Length 0009 (9) │ │ │ │ +33051 Flags 03 (3) 'Modification Access' │ │ │ │ +33052 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +33056 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3305A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3305C Length 000B (11) │ │ │ │ +3305E Version 01 (1) │ │ │ │ +3305F UID Size 04 (4) │ │ │ │ +33060 UID 00000000 (0) │ │ │ │ +33064 GID Size 04 (4) │ │ │ │ +33065 GID 00000000 (0) │ │ │ │ +33069 PAYLOAD │ │ │ │ + │ │ │ │ +34813 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +34817 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +34818 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +34819 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3481B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3481D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +34821 CRC 5F73B5AE (1601418670) │ │ │ │ +34825 Compressed Size 00001371 (4977) │ │ │ │ +34829 Uncompressed Size 00003B66 (15206) │ │ │ │ +3482D Filename Length 0015 (21) │ │ │ │ +3482F Extra Length 001C (28) │ │ │ │ +34831 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x34831: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +34846 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +34848 Length 0009 (9) │ │ │ │ +3484A Flags 03 (3) 'Modification Access' │ │ │ │ +3484B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3484F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +34853 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +34855 Length 000B (11) │ │ │ │ +34857 Version 01 (1) │ │ │ │ +34858 UID Size 04 (4) │ │ │ │ +34859 UID 00000000 (0) │ │ │ │ +3485D GID Size 04 (4) │ │ │ │ +3485E GID 00000000 (0) │ │ │ │ +34862 PAYLOAD │ │ │ │ + │ │ │ │ +35BD3 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +35BD7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35BD8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35BD9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35BDB Compression Method 0008 (8) 'Deflated' │ │ │ │ +35BDD Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +35BE1 CRC 7FAE09B6 (2142112182) │ │ │ │ +35BE5 Compressed Size 00000AD1 (2769) │ │ │ │ +35BE9 Uncompressed Size 00002135 (8501) │ │ │ │ +35BED Filename Length 0011 (17) │ │ │ │ +35BEF Extra Length 001C (28) │ │ │ │ +35BF1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35BF1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35C02 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35C04 Length 0009 (9) │ │ │ │ +35C06 Flags 03 (3) 'Modification Access' │ │ │ │ +35C07 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +35C0B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +35C0F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35C11 Length 000B (11) │ │ │ │ +35C13 Version 01 (1) │ │ │ │ +35C14 UID Size 04 (4) │ │ │ │ +35C15 UID 00000000 (0) │ │ │ │ +35C19 GID Size 04 (4) │ │ │ │ +35C1A GID 00000000 (0) │ │ │ │ +35C1E PAYLOAD │ │ │ │ + │ │ │ │ +366EF LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +366F3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +366F4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +366F5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +366F7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +366F9 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +366FD CRC 23C85C8D (600333453) │ │ │ │ +36701 Compressed Size 000003FE (1022) │ │ │ │ +36705 Uncompressed Size 00000E99 (3737) │ │ │ │ +36709 Filename Length 0014 (20) │ │ │ │ +3670B Extra Length 001C (28) │ │ │ │ +3670D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3670D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +36721 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +36723 Length 0009 (9) │ │ │ │ +36725 Flags 03 (3) 'Modification Access' │ │ │ │ +36726 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3672A Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3672E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +36730 Length 000B (11) │ │ │ │ +36732 Version 01 (1) │ │ │ │ +36733 UID Size 04 (4) │ │ │ │ +36734 UID 00000000 (0) │ │ │ │ +36738 GID Size 04 (4) │ │ │ │ +36739 GID 00000000 (0) │ │ │ │ +3673D PAYLOAD │ │ │ │ + │ │ │ │ +36B3B LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +36B3F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +36B40 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +36B41 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +36B43 Compression Method 0008 (8) 'Deflated' │ │ │ │ +36B45 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +36B49 CRC EB83ACB7 (3951275191) │ │ │ │ +36B4D Compressed Size 00001261 (4705) │ │ │ │ +36B51 Uncompressed Size 00003469 (13417) │ │ │ │ +36B55 Filename Length 0014 (20) │ │ │ │ +36B57 Extra Length 001C (28) │ │ │ │ +36B59 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x36B59: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +36B6D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +36B6F Length 0009 (9) │ │ │ │ +36B71 Flags 03 (3) 'Modification Access' │ │ │ │ +36B72 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +36B76 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +36B7A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +36B7C Length 000B (11) │ │ │ │ +36B7E Version 01 (1) │ │ │ │ +36B7F UID Size 04 (4) │ │ │ │ +36B80 UID 00000000 (0) │ │ │ │ +36B84 GID Size 04 (4) │ │ │ │ +36B85 GID 00000000 (0) │ │ │ │ +36B89 PAYLOAD │ │ │ │ + │ │ │ │ +37DEA LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +37DEE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +37DEF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +37DF0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +37DF2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +37DF4 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +37DF8 CRC 2E26ED0C (774302988) │ │ │ │ +37DFC Compressed Size 00000ACE (2766) │ │ │ │ +37E00 Uncompressed Size 000022FF (8959) │ │ │ │ +37E04 Filename Length 001B (27) │ │ │ │ +37E06 Extra Length 001C (28) │ │ │ │ +37E08 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x37E08: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +37E23 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +37E25 Length 0009 (9) │ │ │ │ +37E27 Flags 03 (3) 'Modification Access' │ │ │ │ +37E28 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +37E2C Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +37E30 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +37E32 Length 000B (11) │ │ │ │ +37E34 Version 01 (1) │ │ │ │ +37E35 UID Size 04 (4) │ │ │ │ +37E36 UID 00000000 (0) │ │ │ │ +37E3A GID Size 04 (4) │ │ │ │ +37E3B GID 00000000 (0) │ │ │ │ +37E3F PAYLOAD │ │ │ │ │ │ │ │ 3890D LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ 38911 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 38912 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 38913 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 38915 Compression Method 0008 (8) 'Deflated' │ │ │ │ -38917 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3891B CRC 7FE9699A (2146003354) │ │ │ │ -3891F Compressed Size 00000A8D (2701) │ │ │ │ +38917 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +3891B CRC 944EC433 (2488190003) │ │ │ │ +3891F Compressed Size 00000A8E (2702) │ │ │ │ 38923 Uncompressed Size 0000237A (9082) │ │ │ │ 38927 Filename Length 0013 (19) │ │ │ │ 38929 Extra Length 001C (28) │ │ │ │ 3892B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x3892B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 3893E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 38940 Length 0009 (9) │ │ │ │ 38942 Flags 03 (3) 'Modification Access' │ │ │ │ -38943 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -38947 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +38943 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +38947 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 3894B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 3894D Length 000B (11) │ │ │ │ 3894F Version 01 (1) │ │ │ │ 38950 UID Size 04 (4) │ │ │ │ 38951 UID 00000000 (0) │ │ │ │ 38955 GID Size 04 (4) │ │ │ │ 38956 GID 00000000 (0) │ │ │ │ 3895A PAYLOAD │ │ │ │ │ │ │ │ -393E7 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -393EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -393EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -393ED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -393EF Compression Method 0008 (8) 'Deflated' │ │ │ │ -393F1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -393F5 CRC DD9D1818 (3718060056) │ │ │ │ -393F9 Compressed Size 00000F48 (3912) │ │ │ │ -393FD Uncompressed Size 000036F1 (14065) │ │ │ │ -39401 Filename Length 000F (15) │ │ │ │ -39403 Extra Length 001C (28) │ │ │ │ -39405 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x39405: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -39414 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -39416 Length 0009 (9) │ │ │ │ -39418 Flags 03 (3) 'Modification Access' │ │ │ │ -39419 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3941D Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -39421 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -39423 Length 000B (11) │ │ │ │ -39425 Version 01 (1) │ │ │ │ -39426 UID Size 04 (4) │ │ │ │ -39427 UID 00000000 (0) │ │ │ │ -3942B GID Size 04 (4) │ │ │ │ -3942C GID 00000000 (0) │ │ │ │ -39430 PAYLOAD │ │ │ │ +393E8 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +393EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +393ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +393EE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +393F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +393F2 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +393F6 CRC 651D575F (1696421727) │ │ │ │ +393FA Compressed Size 00000F47 (3911) │ │ │ │ +393FE Uncompressed Size 000036F1 (14065) │ │ │ │ +39402 Filename Length 000F (15) │ │ │ │ +39404 Extra Length 001C (28) │ │ │ │ +39406 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x39406: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +39415 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +39417 Length 0009 (9) │ │ │ │ +39419 Flags 03 (3) 'Modification Access' │ │ │ │ +3941A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3941E Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +39422 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +39424 Length 000B (11) │ │ │ │ +39426 Version 01 (1) │ │ │ │ +39427 UID Size 04 (4) │ │ │ │ +39428 UID 00000000 (0) │ │ │ │ +3942C GID Size 04 (4) │ │ │ │ +3942D GID 00000000 (0) │ │ │ │ +39431 PAYLOAD │ │ │ │ │ │ │ │ 3A378 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ 3A37C Extract Zip Spec 14 (20) '2.0' │ │ │ │ 3A37D Extract OS 00 (0) 'MS-DOS' │ │ │ │ 3A37E General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 3A380 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3A382 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ +3A382 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ 3A386 CRC 54672E6E (1416048238) │ │ │ │ 3A38A Compressed Size 0000066A (1642) │ │ │ │ 3A38E Uncompressed Size 000018DF (6367) │ │ │ │ 3A392 Filename Length 000F (15) │ │ │ │ 3A394 Extra Length 001C (28) │ │ │ │ 3A396 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x3A396: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 3A3A5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 3A3A7 Length 0009 (9) │ │ │ │ 3A3A9 Flags 03 (3) 'Modification Access' │ │ │ │ -3A3AA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3A3AE Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +3A3AA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3A3AE Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 3A3B2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 3A3B4 Length 000B (11) │ │ │ │ 3A3B6 Version 01 (1) │ │ │ │ 3A3B7 UID Size 04 (4) │ │ │ │ 3A3B8 UID 00000000 (0) │ │ │ │ 3A3BC GID Size 04 (4) │ │ │ │ 3A3BD GID 00000000 (0) │ │ │ │ @@ -992,5288 +992,5288 @@ │ │ │ │ │ │ │ │ 3AA2B LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ 3AA2F Extract Zip Spec 14 (20) '2.0' │ │ │ │ 3AA30 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 3AA31 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 3AA33 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3AA35 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3AA39 CRC 8AF71E36 (2331450934) │ │ │ │ -3AA3D Compressed Size 00001A49 (6729) │ │ │ │ +3AA35 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +3AA39 CRC 0CA4F90A (212138250) │ │ │ │ +3AA3D Compressed Size 00001A47 (6727) │ │ │ │ 3AA41 Uncompressed Size 000064F2 (25842) │ │ │ │ 3AA45 Filename Length 0013 (19) │ │ │ │ 3AA47 Extra Length 001C (28) │ │ │ │ 3AA49 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x3AA49: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 3AA5C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 3AA5E Length 0009 (9) │ │ │ │ 3AA60 Flags 03 (3) 'Modification Access' │ │ │ │ -3AA61 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3AA65 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ +3AA61 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3AA65 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ 3AA69 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 3AA6B Length 000B (11) │ │ │ │ 3AA6D Version 01 (1) │ │ │ │ 3AA6E UID Size 04 (4) │ │ │ │ 3AA6F UID 00000000 (0) │ │ │ │ 3AA73 GID Size 04 (4) │ │ │ │ 3AA74 GID 00000000 (0) │ │ │ │ 3AA78 PAYLOAD │ │ │ │ │ │ │ │ -3C4C1 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -3C4C5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3C4C6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3C4C7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3C4C9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3C4CB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3C4CF CRC D1448544 (3510928708) │ │ │ │ -3C4D3 Compressed Size 000009A5 (2469) │ │ │ │ -3C4D7 Uncompressed Size 00001B64 (7012) │ │ │ │ -3C4DB Filename Length 0010 (16) │ │ │ │ -3C4DD Extra Length 001C (28) │ │ │ │ -3C4DF Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C4DF: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C4EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C4F1 Length 0009 (9) │ │ │ │ -3C4F3 Flags 03 (3) 'Modification Access' │ │ │ │ -3C4F4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3C4F8 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3C4FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C4FE Length 000B (11) │ │ │ │ -3C500 Version 01 (1) │ │ │ │ -3C501 UID Size 04 (4) │ │ │ │ -3C502 UID 00000000 (0) │ │ │ │ -3C506 GID Size 04 (4) │ │ │ │ -3C507 GID 00000000 (0) │ │ │ │ -3C50B PAYLOAD │ │ │ │ - │ │ │ │ -3CEB0 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -3CEB4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3CEB5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3CEB6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3CEB8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3CEBA Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3CEBE CRC 3E5AEB62 (1046145890) │ │ │ │ -3CEC2 Compressed Size 000006B7 (1719) │ │ │ │ -3CEC6 Uncompressed Size 00001565 (5477) │ │ │ │ -3CECA Filename Length 0012 (18) │ │ │ │ -3CECC Extra Length 001C (28) │ │ │ │ -3CECE Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3CECE: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3CEE0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3CEE2 Length 0009 (9) │ │ │ │ -3CEE4 Flags 03 (3) 'Modification Access' │ │ │ │ -3CEE5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3CEE9 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3CEED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3CEEF Length 000B (11) │ │ │ │ -3CEF1 Version 01 (1) │ │ │ │ -3CEF2 UID Size 04 (4) │ │ │ │ -3CEF3 UID 00000000 (0) │ │ │ │ -3CEF7 GID Size 04 (4) │ │ │ │ -3CEF8 GID 00000000 (0) │ │ │ │ -3CEFC PAYLOAD │ │ │ │ - │ │ │ │ -3D5B3 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -3D5B7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3D5B8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3D5B9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3D5BB Compression Method 0008 (8) 'Deflated' │ │ │ │ -3D5BD Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -3D5C1 CRC C50CE88C (3305957516) │ │ │ │ -3D5C5 Compressed Size 00002A1A (10778) │ │ │ │ -3D5C9 Uncompressed Size 0000B1C5 (45509) │ │ │ │ -3D5CD Filename Length 0010 (16) │ │ │ │ -3D5CF Extra Length 001C (28) │ │ │ │ -3D5D1 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3D5D1: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3D5E1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3D5E3 Length 0009 (9) │ │ │ │ -3D5E5 Flags 03 (3) 'Modification Access' │ │ │ │ -3D5E6 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3D5EA Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -3D5EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3D5F0 Length 000B (11) │ │ │ │ -3D5F2 Version 01 (1) │ │ │ │ -3D5F3 UID Size 04 (4) │ │ │ │ -3D5F4 UID 00000000 (0) │ │ │ │ -3D5F8 GID Size 04 (4) │ │ │ │ -3D5F9 GID 00000000 (0) │ │ │ │ -3D5FD PAYLOAD │ │ │ │ - │ │ │ │ -40017 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -4001B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4001C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4001D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4001F Compression Method 0008 (8) 'Deflated' │ │ │ │ -40021 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -40025 CRC C59A1802 (3315210242) │ │ │ │ -40029 Compressed Size 00001E84 (7812) │ │ │ │ -4002D Uncompressed Size 00009AAA (39594) │ │ │ │ -40031 Filename Length 0012 (18) │ │ │ │ -40033 Extra Length 001C (28) │ │ │ │ -40035 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x40035: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40047 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40049 Length 0009 (9) │ │ │ │ -4004B Flags 03 (3) 'Modification Access' │ │ │ │ -4004C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -40050 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -40054 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40056 Length 000B (11) │ │ │ │ -40058 Version 01 (1) │ │ │ │ -40059 UID Size 04 (4) │ │ │ │ -4005A UID 00000000 (0) │ │ │ │ -4005E GID Size 04 (4) │ │ │ │ -4005F GID 00000000 (0) │ │ │ │ -40063 PAYLOAD │ │ │ │ - │ │ │ │ -41EE7 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -41EEB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -41EEC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -41EED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -41EEF Compression Method 0008 (8) 'Deflated' │ │ │ │ -41EF1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -41EF5 CRC A34909C0 (2739472832) │ │ │ │ -41EF9 Compressed Size 00001477 (5239) │ │ │ │ -41EFD Uncompressed Size 00007ACF (31439) │ │ │ │ -41F01 Filename Length 0018 (24) │ │ │ │ -41F03 Extra Length 001C (28) │ │ │ │ -41F05 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x41F05: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -41F1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -41F1F Length 0009 (9) │ │ │ │ -41F21 Flags 03 (3) 'Modification Access' │ │ │ │ -41F22 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -41F26 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -41F2A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -41F2C Length 000B (11) │ │ │ │ -41F2E Version 01 (1) │ │ │ │ -41F2F UID Size 04 (4) │ │ │ │ -41F30 UID 00000000 (0) │ │ │ │ -41F34 GID Size 04 (4) │ │ │ │ -41F35 GID 00000000 (0) │ │ │ │ -41F39 PAYLOAD │ │ │ │ - │ │ │ │ -433B0 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -433B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -433B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -433B6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -433B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -433BA Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -433BE CRC C605A326 (3322258214) │ │ │ │ -433C2 Compressed Size 000018CF (6351) │ │ │ │ -433C6 Uncompressed Size 0000A7F4 (42996) │ │ │ │ -433CA Filename Length 001F (31) │ │ │ │ -433CC Extra Length 001C (28) │ │ │ │ -433CE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x433CE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -433ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -433EF Length 0009 (9) │ │ │ │ -433F1 Flags 03 (3) 'Modification Access' │ │ │ │ -433F2 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -433F6 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -433FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -433FC Length 000B (11) │ │ │ │ -433FE Version 01 (1) │ │ │ │ -433FF UID Size 04 (4) │ │ │ │ -43400 UID 00000000 (0) │ │ │ │ -43404 GID Size 04 (4) │ │ │ │ -43405 GID 00000000 (0) │ │ │ │ -43409 PAYLOAD │ │ │ │ - │ │ │ │ -44CD8 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -44CDC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -44CDD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -44CDE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -44CE0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -44CE2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -44CE6 CRC FFAF198F (4289665423) │ │ │ │ -44CEA Compressed Size 000003F6 (1014) │ │ │ │ -44CEE Uncompressed Size 000008A3 (2211) │ │ │ │ -44CF2 Filename Length 001E (30) │ │ │ │ -44CF4 Extra Length 001C (28) │ │ │ │ -44CF6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x44CF6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -44D14 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -44D16 Length 0009 (9) │ │ │ │ -44D18 Flags 03 (3) 'Modification Access' │ │ │ │ -44D19 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -44D1D Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -44D21 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -44D23 Length 000B (11) │ │ │ │ -44D25 Version 01 (1) │ │ │ │ -44D26 UID Size 04 (4) │ │ │ │ -44D27 UID 00000000 (0) │ │ │ │ -44D2B GID Size 04 (4) │ │ │ │ -44D2C GID 00000000 (0) │ │ │ │ -44D30 PAYLOAD │ │ │ │ - │ │ │ │ -45126 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -4512A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4512B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4512C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4512E Compression Method 0008 (8) 'Deflated' │ │ │ │ -45130 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -45134 CRC 32E3FA13 (853801491) │ │ │ │ -45138 Compressed Size 00004292 (17042) │ │ │ │ -4513C Uncompressed Size 0000D8DC (55516) │ │ │ │ -45140 Filename Length 0013 (19) │ │ │ │ -45142 Extra Length 001C (28) │ │ │ │ -45144 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45144: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45157 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -45159 Length 0009 (9) │ │ │ │ -4515B Flags 03 (3) 'Modification Access' │ │ │ │ -4515C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -45160 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -45164 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45166 Length 000B (11) │ │ │ │ -45168 Version 01 (1) │ │ │ │ -45169 UID Size 04 (4) │ │ │ │ -4516A UID 00000000 (0) │ │ │ │ -4516E GID Size 04 (4) │ │ │ │ -4516F GID 00000000 (0) │ │ │ │ -45173 PAYLOAD │ │ │ │ - │ │ │ │ -49405 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -49409 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4940A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4940B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4940D Compression Method 0008 (8) 'Deflated' │ │ │ │ -4940F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -49413 CRC 9F6AFEBF (2674589375) │ │ │ │ -49417 Compressed Size 000026C3 (9923) │ │ │ │ -4941B Uncompressed Size 00006E45 (28229) │ │ │ │ -4941F Filename Length 0019 (25) │ │ │ │ -49421 Extra Length 001C (28) │ │ │ │ -49423 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x49423: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4943C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4943E Length 0009 (9) │ │ │ │ -49440 Flags 03 (3) 'Modification Access' │ │ │ │ -49441 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -49445 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -49449 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4944B Length 000B (11) │ │ │ │ -4944D Version 01 (1) │ │ │ │ -4944E UID Size 04 (4) │ │ │ │ -4944F UID 00000000 (0) │ │ │ │ -49453 GID Size 04 (4) │ │ │ │ -49454 GID 00000000 (0) │ │ │ │ -49458 PAYLOAD │ │ │ │ - │ │ │ │ -4BB1B LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -4BB1F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4BB20 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4BB21 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4BB23 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4BB25 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -4BB29 CRC 0369577C (57235324) │ │ │ │ -4BB2D Compressed Size 00002739 (10041) │ │ │ │ -4BB31 Uncompressed Size 00008B83 (35715) │ │ │ │ -4BB35 Filename Length 0019 (25) │ │ │ │ -4BB37 Extra Length 001C (28) │ │ │ │ -4BB39 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4BB39: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4BB52 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4BB54 Length 0009 (9) │ │ │ │ -4BB56 Flags 03 (3) 'Modification Access' │ │ │ │ -4BB57 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4BB5B Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4BB5F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4BB61 Length 000B (11) │ │ │ │ -4BB63 Version 01 (1) │ │ │ │ -4BB64 UID Size 04 (4) │ │ │ │ -4BB65 UID 00000000 (0) │ │ │ │ -4BB69 GID Size 04 (4) │ │ │ │ -4BB6A GID 00000000 (0) │ │ │ │ -4BB6E PAYLOAD │ │ │ │ - │ │ │ │ -4E2A7 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -4E2AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4E2AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4E2AD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4E2AF Compression Method 0008 (8) 'Deflated' │ │ │ │ -4E2B1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -4E2B5 CRC 8B9BB6EF (2342237935) │ │ │ │ -4E2B9 Compressed Size 00000CF1 (3313) │ │ │ │ -4E2BD Uncompressed Size 0000517A (20858) │ │ │ │ -4E2C1 Filename Length 0021 (33) │ │ │ │ -4E2C3 Extra Length 001C (28) │ │ │ │ -4E2C5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4E2C5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4E2E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4E2E8 Length 0009 (9) │ │ │ │ -4E2EA Flags 03 (3) 'Modification Access' │ │ │ │ -4E2EB Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4E2EF Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4E2F3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4E2F5 Length 000B (11) │ │ │ │ -4E2F7 Version 01 (1) │ │ │ │ -4E2F8 UID Size 04 (4) │ │ │ │ -4E2F9 UID 00000000 (0) │ │ │ │ -4E2FD GID Size 04 (4) │ │ │ │ -4E2FE GID 00000000 (0) │ │ │ │ -4E302 PAYLOAD │ │ │ │ - │ │ │ │ -4EFF3 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -4EFF7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4EFF8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4EFF9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4EFFB Compression Method 0008 (8) 'Deflated' │ │ │ │ -4EFFD Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -4F001 CRC 8F01E69C (2399266460) │ │ │ │ -4F005 Compressed Size 00000468 (1128) │ │ │ │ -4F009 Uncompressed Size 00000931 (2353) │ │ │ │ -4F00D Filename Length 001B (27) │ │ │ │ -4F00F Extra Length 001C (28) │ │ │ │ -4F011 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F011: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F02C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F02E Length 0009 (9) │ │ │ │ -4F030 Flags 03 (3) 'Modification Access' │ │ │ │ -4F031 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4F035 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4F039 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F03B Length 000B (11) │ │ │ │ -4F03D Version 01 (1) │ │ │ │ -4F03E UID Size 04 (4) │ │ │ │ -4F03F UID 00000000 (0) │ │ │ │ -4F043 GID Size 04 (4) │ │ │ │ -4F044 GID 00000000 (0) │ │ │ │ -4F048 PAYLOAD │ │ │ │ - │ │ │ │ -4F4B0 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -4F4B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F4B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F4B6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F4B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F4BA Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -4F4BE CRC 2A5112D6 (709956310) │ │ │ │ -4F4C2 Compressed Size 000016F1 (5873) │ │ │ │ -4F4C6 Uncompressed Size 00007A6D (31341) │ │ │ │ -4F4CA Filename Length 001F (31) │ │ │ │ -4F4CC Extra Length 001C (28) │ │ │ │ -4F4CE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F4CE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F4ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F4EF Length 0009 (9) │ │ │ │ -4F4F1 Flags 03 (3) 'Modification Access' │ │ │ │ -4F4F2 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4F4F6 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -4F4FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F4FC Length 000B (11) │ │ │ │ -4F4FE Version 01 (1) │ │ │ │ -4F4FF UID Size 04 (4) │ │ │ │ -4F500 UID 00000000 (0) │ │ │ │ -4F504 GID Size 04 (4) │ │ │ │ -4F505 GID 00000000 (0) │ │ │ │ -4F509 PAYLOAD │ │ │ │ - │ │ │ │ -50BFA LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -50BFE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -50BFF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -50C00 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -50C02 Compression Method 0008 (8) 'Deflated' │ │ │ │ -50C04 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -50C08 CRC 1BA8E4E8 (464053480) │ │ │ │ -50C0C Compressed Size 0000416A (16746) │ │ │ │ -50C10 Uncompressed Size 0001CF93 (118675) │ │ │ │ -50C14 Filename Length 0010 (16) │ │ │ │ -50C16 Extra Length 001C (28) │ │ │ │ -50C18 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x50C18: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -50C28 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -50C2A Length 0009 (9) │ │ │ │ -50C2C Flags 03 (3) 'Modification Access' │ │ │ │ -50C2D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -50C31 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -50C35 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -50C37 Length 000B (11) │ │ │ │ -50C39 Version 01 (1) │ │ │ │ -50C3A UID Size 04 (4) │ │ │ │ -50C3B UID 00000000 (0) │ │ │ │ -50C3F GID Size 04 (4) │ │ │ │ -50C40 GID 00000000 (0) │ │ │ │ -50C44 PAYLOAD │ │ │ │ - │ │ │ │ -54DAE LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -54DB2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -54DB3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -54DB4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -54DB6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -54DB8 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -54DBC CRC 2020DAF6 (539024118) │ │ │ │ -54DC0 Compressed Size 00000A94 (2708) │ │ │ │ -54DC4 Uncompressed Size 00002105 (8453) │ │ │ │ -54DC8 Filename Length 0014 (20) │ │ │ │ -54DCA Extra Length 001C (28) │ │ │ │ -54DCC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x54DCC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -54DE0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -54DE2 Length 0009 (9) │ │ │ │ -54DE4 Flags 03 (3) 'Modification Access' │ │ │ │ -54DE5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -54DE9 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -54DED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -54DEF Length 000B (11) │ │ │ │ -54DF1 Version 01 (1) │ │ │ │ -54DF2 UID Size 04 (4) │ │ │ │ -54DF3 UID 00000000 (0) │ │ │ │ -54DF7 GID Size 04 (4) │ │ │ │ -54DF8 GID 00000000 (0) │ │ │ │ -54DFC PAYLOAD │ │ │ │ - │ │ │ │ -55890 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -55894 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -55895 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -55896 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -55898 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5589A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -5589E CRC F1FD1FA8 (4059897768) │ │ │ │ -558A2 Compressed Size 0000AC9D (44189) │ │ │ │ -558A6 Uncompressed Size 0003E418 (255000) │ │ │ │ -558AA Filename Length 0017 (23) │ │ │ │ -558AC Extra Length 001C (28) │ │ │ │ -558AE Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x558AE: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -558C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -558C7 Length 0009 (9) │ │ │ │ -558C9 Flags 03 (3) 'Modification Access' │ │ │ │ -558CA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -558CE Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -558D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -558D4 Length 000B (11) │ │ │ │ -558D6 Version 01 (1) │ │ │ │ -558D7 UID Size 04 (4) │ │ │ │ -558D8 UID 00000000 (0) │ │ │ │ -558DC GID Size 04 (4) │ │ │ │ -558DD GID 00000000 (0) │ │ │ │ -558E1 PAYLOAD │ │ │ │ - │ │ │ │ -6057E LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -60582 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -60583 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -60584 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -60586 Compression Method 0008 (8) 'Deflated' │ │ │ │ -60588 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6058C CRC 97230023 (2535653411) │ │ │ │ -60590 Compressed Size 00000401 (1025) │ │ │ │ -60594 Uncompressed Size 0000093D (2365) │ │ │ │ -60598 Filename Length 0013 (19) │ │ │ │ -6059A Extra Length 001C (28) │ │ │ │ -6059C Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6059C: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -605AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -605B1 Length 0009 (9) │ │ │ │ -605B3 Flags 03 (3) 'Modification Access' │ │ │ │ -605B4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -605B8 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -605BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -605BE Length 000B (11) │ │ │ │ -605C0 Version 01 (1) │ │ │ │ -605C1 UID Size 04 (4) │ │ │ │ -605C2 UID 00000000 (0) │ │ │ │ -605C6 GID Size 04 (4) │ │ │ │ -605C7 GID 00000000 (0) │ │ │ │ -605CB PAYLOAD │ │ │ │ - │ │ │ │ -609CC LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -609D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -609D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -609D2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -609D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -609D6 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -609DA CRC 15AD4D8E (363679118) │ │ │ │ -609DE Compressed Size 000014DB (5339) │ │ │ │ -609E2 Uncompressed Size 0000687B (26747) │ │ │ │ -609E6 Filename Length 0012 (18) │ │ │ │ -609E8 Extra Length 001C (28) │ │ │ │ -609EA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x609EA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -609FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -609FE Length 0009 (9) │ │ │ │ -60A00 Flags 03 (3) 'Modification Access' │ │ │ │ -60A01 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -60A05 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -60A09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -60A0B Length 000B (11) │ │ │ │ -60A0D Version 01 (1) │ │ │ │ -60A0E UID Size 04 (4) │ │ │ │ -60A0F UID 00000000 (0) │ │ │ │ -60A13 GID Size 04 (4) │ │ │ │ -60A14 GID 00000000 (0) │ │ │ │ -60A18 PAYLOAD │ │ │ │ - │ │ │ │ -61EF3 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -61EF7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -61EF8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -61EF9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -61EFB Compression Method 0008 (8) 'Deflated' │ │ │ │ -61EFD Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -61F01 CRC BDF3884F (3186853967) │ │ │ │ -61F05 Compressed Size 000011EB (4587) │ │ │ │ -61F09 Uncompressed Size 000040F5 (16629) │ │ │ │ -61F0D Filename Length 0012 (18) │ │ │ │ -61F0F Extra Length 001C (28) │ │ │ │ -61F11 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x61F11: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -61F23 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -61F25 Length 0009 (9) │ │ │ │ -61F27 Flags 03 (3) 'Modification Access' │ │ │ │ -61F28 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -61F2C Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -61F30 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -61F32 Length 000B (11) │ │ │ │ -61F34 Version 01 (1) │ │ │ │ -61F35 UID Size 04 (4) │ │ │ │ -61F36 UID 00000000 (0) │ │ │ │ -61F3A GID Size 04 (4) │ │ │ │ -61F3B GID 00000000 (0) │ │ │ │ -61F3F PAYLOAD │ │ │ │ - │ │ │ │ -6312A LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -6312E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6312F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -63130 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -63132 Compression Method 0008 (8) 'Deflated' │ │ │ │ -63134 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -63138 CRC 2C7ABAF2 (746240754) │ │ │ │ -6313C Compressed Size 000009DA (2522) │ │ │ │ -63140 Uncompressed Size 00003529 (13609) │ │ │ │ -63144 Filename Length 0019 (25) │ │ │ │ -63146 Extra Length 001C (28) │ │ │ │ -63148 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x63148: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63161 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -63163 Length 0009 (9) │ │ │ │ -63165 Flags 03 (3) 'Modification Access' │ │ │ │ -63166 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6316A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6316E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63170 Length 000B (11) │ │ │ │ -63172 Version 01 (1) │ │ │ │ -63173 UID Size 04 (4) │ │ │ │ -63174 UID 00000000 (0) │ │ │ │ -63178 GID Size 04 (4) │ │ │ │ -63179 GID 00000000 (0) │ │ │ │ -6317D PAYLOAD │ │ │ │ - │ │ │ │ -63B57 LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -63B5B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -63B5C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -63B5D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -63B5F Compression Method 0008 (8) 'Deflated' │ │ │ │ -63B61 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -63B65 CRC D69E8D28 (3600715048) │ │ │ │ -63B69 Compressed Size 000018B1 (6321) │ │ │ │ -63B6D Uncompressed Size 0000A605 (42501) │ │ │ │ -63B71 Filename Length 0019 (25) │ │ │ │ -63B73 Extra Length 001C (28) │ │ │ │ -63B75 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x63B75: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63B8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -63B90 Length 0009 (9) │ │ │ │ -63B92 Flags 03 (3) 'Modification Access' │ │ │ │ -63B93 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -63B97 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -63B9B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63B9D Length 000B (11) │ │ │ │ -63B9F Version 01 (1) │ │ │ │ -63BA0 UID Size 04 (4) │ │ │ │ -63BA1 UID 00000000 (0) │ │ │ │ -63BA5 GID Size 04 (4) │ │ │ │ -63BA6 GID 00000000 (0) │ │ │ │ -63BAA PAYLOAD │ │ │ │ - │ │ │ │ -6545B LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -6545F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -65460 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -65461 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -65463 Compression Method 0008 (8) 'Deflated' │ │ │ │ -65465 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -65469 CRC 47E0D6DC (1205917404) │ │ │ │ -6546D Compressed Size 0000177E (6014) │ │ │ │ -65471 Uncompressed Size 0000472C (18220) │ │ │ │ -65475 Filename Length 0014 (20) │ │ │ │ -65477 Extra Length 001C (28) │ │ │ │ -65479 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x65479: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6548D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6548F Length 0009 (9) │ │ │ │ -65491 Flags 03 (3) 'Modification Access' │ │ │ │ -65492 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -65496 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6549A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6549C Length 000B (11) │ │ │ │ -6549E Version 01 (1) │ │ │ │ -6549F UID Size 04 (4) │ │ │ │ -654A0 UID 00000000 (0) │ │ │ │ -654A4 GID Size 04 (4) │ │ │ │ -654A5 GID 00000000 (0) │ │ │ │ -654A9 PAYLOAD │ │ │ │ - │ │ │ │ -66C27 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -66C2B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -66C2C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -66C2D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -66C2F Compression Method 0008 (8) 'Deflated' │ │ │ │ -66C31 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -66C35 CRC EDF51269 (3992261225) │ │ │ │ -66C39 Compressed Size 0000040B (1035) │ │ │ │ -66C3D Uncompressed Size 00000825 (2085) │ │ │ │ -66C41 Filename Length 001C (28) │ │ │ │ -66C43 Extra Length 001C (28) │ │ │ │ -66C45 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x66C45: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -66C61 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -66C63 Length 0009 (9) │ │ │ │ -66C65 Flags 03 (3) 'Modification Access' │ │ │ │ -66C66 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -66C6A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -66C6E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -66C70 Length 000B (11) │ │ │ │ -66C72 Version 01 (1) │ │ │ │ -66C73 UID Size 04 (4) │ │ │ │ -66C74 UID 00000000 (0) │ │ │ │ -66C78 GID Size 04 (4) │ │ │ │ -66C79 GID 00000000 (0) │ │ │ │ -66C7D PAYLOAD │ │ │ │ - │ │ │ │ -67088 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -6708C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6708D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6708E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -67090 Compression Method 0008 (8) 'Deflated' │ │ │ │ -67092 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -67096 CRC AC935B99 (2895338393) │ │ │ │ -6709A Compressed Size 00002483 (9347) │ │ │ │ -6709E Uncompressed Size 0000B56F (46447) │ │ │ │ -670A2 Filename Length 001F (31) │ │ │ │ -670A4 Extra Length 001C (28) │ │ │ │ -670A6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x670A6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -670C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -670C7 Length 0009 (9) │ │ │ │ -670C9 Flags 03 (3) 'Modification Access' │ │ │ │ -670CA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -670CE Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -670D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -670D4 Length 000B (11) │ │ │ │ -670D6 Version 01 (1) │ │ │ │ -670D7 UID Size 04 (4) │ │ │ │ -670D8 UID 00000000 (0) │ │ │ │ -670DC GID Size 04 (4) │ │ │ │ -670DD GID 00000000 (0) │ │ │ │ -670E1 PAYLOAD │ │ │ │ - │ │ │ │ -69564 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -69568 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -69569 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6956A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6956C Compression Method 0008 (8) 'Deflated' │ │ │ │ -6956E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -69572 CRC EE677FF5 (3999760373) │ │ │ │ -69576 Compressed Size 00000E81 (3713) │ │ │ │ -6957A Uncompressed Size 000052D9 (21209) │ │ │ │ -6957E Filename Length 001F (31) │ │ │ │ -69580 Extra Length 001C (28) │ │ │ │ -69582 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x69582: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -695A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -695A3 Length 0009 (9) │ │ │ │ -695A5 Flags 03 (3) 'Modification Access' │ │ │ │ -695A6 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -695AA Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -695AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -695B0 Length 000B (11) │ │ │ │ -695B2 Version 01 (1) │ │ │ │ -695B3 UID Size 04 (4) │ │ │ │ -695B4 UID 00000000 (0) │ │ │ │ -695B8 GID Size 04 (4) │ │ │ │ -695B9 GID 00000000 (0) │ │ │ │ -695BD PAYLOAD │ │ │ │ - │ │ │ │ -6A43E LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -6A442 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6A443 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6A444 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6A446 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6A448 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6A44C CRC 651C981F (1696372767) │ │ │ │ -6A450 Compressed Size 00000A43 (2627) │ │ │ │ -6A454 Uncompressed Size 0000247A (9338) │ │ │ │ -6A458 Filename Length 0013 (19) │ │ │ │ -6A45A Extra Length 001C (28) │ │ │ │ -6A45C Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6A45C: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6A46F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6A471 Length 0009 (9) │ │ │ │ -6A473 Flags 03 (3) 'Modification Access' │ │ │ │ -6A474 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6A478 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6A47C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6A47E Length 000B (11) │ │ │ │ -6A480 Version 01 (1) │ │ │ │ -6A481 UID Size 04 (4) │ │ │ │ -6A482 UID 00000000 (0) │ │ │ │ -6A486 GID Size 04 (4) │ │ │ │ -6A487 GID 00000000 (0) │ │ │ │ -6A48B PAYLOAD │ │ │ │ - │ │ │ │ -6AECE LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -6AED2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6AED3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6AED4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6AED6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6AED8 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6AEDC CRC 6A1CF0DA (1780281562) │ │ │ │ -6AEE0 Compressed Size 00002486 (9350) │ │ │ │ -6AEE4 Uncompressed Size 0000B84C (47180) │ │ │ │ -6AEE8 Filename Length 0019 (25) │ │ │ │ -6AEEA Extra Length 001C (28) │ │ │ │ -6AEEC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6AEEC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6AF05 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6AF07 Length 0009 (9) │ │ │ │ -6AF09 Flags 03 (3) 'Modification Access' │ │ │ │ -6AF0A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6AF0E Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6AF12 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6AF14 Length 000B (11) │ │ │ │ -6AF16 Version 01 (1) │ │ │ │ -6AF17 UID Size 04 (4) │ │ │ │ -6AF18 UID 00000000 (0) │ │ │ │ -6AF1C GID Size 04 (4) │ │ │ │ -6AF1D GID 00000000 (0) │ │ │ │ -6AF21 PAYLOAD │ │ │ │ - │ │ │ │ -6D3A7 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -6D3AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6D3AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6D3AD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6D3AF Compression Method 0008 (8) 'Deflated' │ │ │ │ -6D3B1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6D3B5 CRC E9583C9A (3914874010) │ │ │ │ -6D3B9 Compressed Size 00000EF9 (3833) │ │ │ │ -6D3BD Uncompressed Size 00003A2C (14892) │ │ │ │ -6D3C1 Filename Length 0024 (36) │ │ │ │ -6D3C3 Extra Length 001C (28) │ │ │ │ -6D3C5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6D3C5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6D3E9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6D3EB Length 0009 (9) │ │ │ │ -6D3ED Flags 03 (3) 'Modification Access' │ │ │ │ -6D3EE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6D3F2 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6D3F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6D3F8 Length 000B (11) │ │ │ │ -6D3FA Version 01 (1) │ │ │ │ -6D3FB UID Size 04 (4) │ │ │ │ -6D3FC UID 00000000 (0) │ │ │ │ -6D400 GID Size 04 (4) │ │ │ │ -6D401 GID 00000000 (0) │ │ │ │ -6D405 PAYLOAD │ │ │ │ - │ │ │ │ -6E2FE LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -6E302 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6E303 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6E304 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6E306 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6E308 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6E30C CRC 0D165AC6 (219568838) │ │ │ │ -6E310 Compressed Size 00001AC1 (6849) │ │ │ │ -6E314 Uncompressed Size 00005EDC (24284) │ │ │ │ -6E318 Filename Length 0017 (23) │ │ │ │ -6E31A Extra Length 001C (28) │ │ │ │ -6E31C Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6E31C: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6E333 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6E335 Length 0009 (9) │ │ │ │ -6E337 Flags 03 (3) 'Modification Access' │ │ │ │ -6E338 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6E33C Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6E340 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6E342 Length 000B (11) │ │ │ │ -6E344 Version 01 (1) │ │ │ │ -6E345 UID Size 04 (4) │ │ │ │ -6E346 UID 00000000 (0) │ │ │ │ -6E34A GID Size 04 (4) │ │ │ │ -6E34B GID 00000000 (0) │ │ │ │ -6E34F PAYLOAD │ │ │ │ - │ │ │ │ -6FE10 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -6FE14 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6FE15 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6FE16 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6FE18 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6FE1A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -6FE1E CRC 11E32AF1 (300100337) │ │ │ │ -6FE22 Compressed Size 00000ED3 (3795) │ │ │ │ -6FE26 Uncompressed Size 000038E2 (14562) │ │ │ │ -6FE2A Filename Length 0023 (35) │ │ │ │ -6FE2C Extra Length 001C (28) │ │ │ │ -6FE2E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6FE2E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6FE51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6FE53 Length 0009 (9) │ │ │ │ -6FE55 Flags 03 (3) 'Modification Access' │ │ │ │ -6FE56 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6FE5A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -6FE5E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6FE60 Length 000B (11) │ │ │ │ -6FE62 Version 01 (1) │ │ │ │ -6FE63 UID Size 04 (4) │ │ │ │ -6FE64 UID 00000000 (0) │ │ │ │ -6FE68 GID Size 04 (4) │ │ │ │ -6FE69 GID 00000000 (0) │ │ │ │ -6FE6D PAYLOAD │ │ │ │ - │ │ │ │ -70D40 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -70D44 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -70D45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -70D46 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -70D48 Compression Method 0008 (8) 'Deflated' │ │ │ │ -70D4A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -70D4E CRC 2DB7929F (767005343) │ │ │ │ -70D52 Compressed Size 00000113 (275) │ │ │ │ -70D56 Uncompressed Size 000001F3 (499) │ │ │ │ -70D5A Filename Length 001B (27) │ │ │ │ -70D5C Extra Length 001C (28) │ │ │ │ -70D5E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x70D5E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -70D79 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -70D7B Length 0009 (9) │ │ │ │ -70D7D Flags 03 (3) 'Modification Access' │ │ │ │ -70D7E Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -70D82 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -70D86 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -70D88 Length 000B (11) │ │ │ │ -70D8A Version 01 (1) │ │ │ │ -70D8B UID Size 04 (4) │ │ │ │ -70D8C UID 00000000 (0) │ │ │ │ -70D90 GID Size 04 (4) │ │ │ │ -70D91 GID 00000000 (0) │ │ │ │ -70D95 PAYLOAD │ │ │ │ - │ │ │ │ -70EA8 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -70EAC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -70EAD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -70EAE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -70EB0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -70EB2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -70EB6 CRC BC4620C3 (3158712515) │ │ │ │ -70EBA Compressed Size 00001892 (6290) │ │ │ │ -70EBE Uncompressed Size 00008FAC (36780) │ │ │ │ -70EC2 Filename Length 001D (29) │ │ │ │ -70EC4 Extra Length 001C (28) │ │ │ │ -70EC6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x70EC6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -70EE3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -70EE5 Length 0009 (9) │ │ │ │ -70EE7 Flags 03 (3) 'Modification Access' │ │ │ │ -70EE8 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -70EEC Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -70EF0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -70EF2 Length 000B (11) │ │ │ │ -70EF4 Version 01 (1) │ │ │ │ -70EF5 UID Size 04 (4) │ │ │ │ -70EF6 UID 00000000 (0) │ │ │ │ -70EFA GID Size 04 (4) │ │ │ │ -70EFB GID 00000000 (0) │ │ │ │ -70EFF PAYLOAD │ │ │ │ - │ │ │ │ -72791 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -72795 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -72796 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -72797 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -72799 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7279B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -7279F CRC 63FED801 (1677645825) │ │ │ │ -727A3 Compressed Size 0000164D (5709) │ │ │ │ -727A7 Uncompressed Size 00003A9B (15003) │ │ │ │ -727AB Filename Length 0015 (21) │ │ │ │ -727AD Extra Length 001C (28) │ │ │ │ -727AF Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x727AF: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -727C4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -727C6 Length 0009 (9) │ │ │ │ -727C8 Flags 03 (3) 'Modification Access' │ │ │ │ -727C9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -727CD Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -727D1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -727D3 Length 000B (11) │ │ │ │ -727D5 Version 01 (1) │ │ │ │ -727D6 UID Size 04 (4) │ │ │ │ -727D7 UID 00000000 (0) │ │ │ │ -727DB GID Size 04 (4) │ │ │ │ -727DC GID 00000000 (0) │ │ │ │ -727E0 PAYLOAD │ │ │ │ - │ │ │ │ -73E2D LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -73E31 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -73E32 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -73E33 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -73E35 Compression Method 0008 (8) 'Deflated' │ │ │ │ -73E37 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -73E3B CRC ADE907F6 (2917730294) │ │ │ │ -73E3F Compressed Size 00003B50 (15184) │ │ │ │ -73E43 Uncompressed Size 0001185B (71771) │ │ │ │ -73E47 Filename Length 0016 (22) │ │ │ │ -73E49 Extra Length 001C (28) │ │ │ │ -73E4B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x73E4B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -73E61 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -73E63 Length 0009 (9) │ │ │ │ -73E65 Flags 03 (3) 'Modification Access' │ │ │ │ -73E66 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -73E6A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -73E6E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -73E70 Length 000B (11) │ │ │ │ -73E72 Version 01 (1) │ │ │ │ -73E73 UID Size 04 (4) │ │ │ │ -73E74 UID 00000000 (0) │ │ │ │ -73E78 GID Size 04 (4) │ │ │ │ -73E79 GID 00000000 (0) │ │ │ │ -73E7D PAYLOAD │ │ │ │ - │ │ │ │ -779CD LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -779D1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -779D2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -779D3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -779D5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -779D7 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -779DB CRC 7DF00FFC (2112884732) │ │ │ │ -779DF Compressed Size 00003E85 (16005) │ │ │ │ -779E3 Uncompressed Size 0001C17B (115067) │ │ │ │ -779E7 Filename Length 0019 (25) │ │ │ │ -779E9 Extra Length 001C (28) │ │ │ │ -779EB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x779EB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -77A04 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -77A06 Length 0009 (9) │ │ │ │ -77A08 Flags 03 (3) 'Modification Access' │ │ │ │ -77A09 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -77A0D Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -77A11 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -77A13 Length 000B (11) │ │ │ │ -77A15 Version 01 (1) │ │ │ │ -77A16 UID Size 04 (4) │ │ │ │ -77A17 UID 00000000 (0) │ │ │ │ -77A1B GID Size 04 (4) │ │ │ │ -77A1C GID 00000000 (0) │ │ │ │ -77A20 PAYLOAD │ │ │ │ - │ │ │ │ -7B8A5 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -7B8A9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7B8AA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7B8AB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7B8AD Compression Method 0008 (8) 'Deflated' │ │ │ │ -7B8AF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -7B8B3 CRC 08716215 (141648405) │ │ │ │ -7B8B7 Compressed Size 00000833 (2099) │ │ │ │ -7B8BB Uncompressed Size 00003383 (13187) │ │ │ │ -7B8BF Filename Length 0011 (17) │ │ │ │ -7B8C1 Extra Length 001C (28) │ │ │ │ -7B8C3 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7B8C3: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7B8D4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7B8D6 Length 0009 (9) │ │ │ │ -7B8D8 Flags 03 (3) 'Modification Access' │ │ │ │ -7B8D9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -7B8DD Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -7B8E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7B8E3 Length 000B (11) │ │ │ │ -7B8E5 Version 01 (1) │ │ │ │ -7B8E6 UID Size 04 (4) │ │ │ │ -7B8E7 UID 00000000 (0) │ │ │ │ -7B8EB GID Size 04 (4) │ │ │ │ -7B8EC GID 00000000 (0) │ │ │ │ -7B8F0 PAYLOAD │ │ │ │ - │ │ │ │ -7C123 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -7C127 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7C128 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7C129 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7C12B Compression Method 0008 (8) 'Deflated' │ │ │ │ -7C12D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -7C131 CRC 9B8ACD82 (2609565058) │ │ │ │ -7C135 Compressed Size 00005188 (20872) │ │ │ │ -7C139 Uncompressed Size 0001FB6C (129900) │ │ │ │ -7C13D Filename Length 0015 (21) │ │ │ │ -7C13F Extra Length 001C (28) │ │ │ │ -7C141 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7C141: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7C156 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7C158 Length 0009 (9) │ │ │ │ -7C15A Flags 03 (3) 'Modification Access' │ │ │ │ -7C15B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -7C15F Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -7C163 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7C165 Length 000B (11) │ │ │ │ -7C167 Version 01 (1) │ │ │ │ -7C168 UID Size 04 (4) │ │ │ │ -7C169 UID 00000000 (0) │ │ │ │ -7C16D GID Size 04 (4) │ │ │ │ -7C16E GID 00000000 (0) │ │ │ │ -7C172 PAYLOAD │ │ │ │ - │ │ │ │ -812FA LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -812FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -812FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -81300 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -81302 Compression Method 0008 (8) 'Deflated' │ │ │ │ -81304 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -81308 CRC 535E57D6 (1398691798) │ │ │ │ -8130C Compressed Size 00001B05 (6917) │ │ │ │ -81310 Uncompressed Size 000081CF (33231) │ │ │ │ -81314 Filename Length 0019 (25) │ │ │ │ -81316 Extra Length 001C (28) │ │ │ │ -81318 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81318: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -81331 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -81333 Length 0009 (9) │ │ │ │ -81335 Flags 03 (3) 'Modification Access' │ │ │ │ -81336 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8133A Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8133E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -81340 Length 000B (11) │ │ │ │ -81342 Version 01 (1) │ │ │ │ -81343 UID Size 04 (4) │ │ │ │ -81344 UID 00000000 (0) │ │ │ │ -81348 GID Size 04 (4) │ │ │ │ -81349 GID 00000000 (0) │ │ │ │ -8134D PAYLOAD │ │ │ │ - │ │ │ │ -82E52 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -82E56 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -82E57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -82E58 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -82E5A Compression Method 0008 (8) 'Deflated' │ │ │ │ -82E5C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -82E60 CRC 02D5C73B (47564603) │ │ │ │ -82E64 Compressed Size 00000D96 (3478) │ │ │ │ -82E68 Uncompressed Size 00002E9F (11935) │ │ │ │ -82E6C Filename Length 0018 (24) │ │ │ │ -82E6E Extra Length 001C (28) │ │ │ │ -82E70 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x82E70: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -82E88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -82E8A Length 0009 (9) │ │ │ │ -82E8C Flags 03 (3) 'Modification Access' │ │ │ │ -82E8D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -82E91 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -82E95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -82E97 Length 000B (11) │ │ │ │ -82E99 Version 01 (1) │ │ │ │ -82E9A UID Size 04 (4) │ │ │ │ -82E9B UID 00000000 (0) │ │ │ │ -82E9F GID Size 04 (4) │ │ │ │ -82EA0 GID 00000000 (0) │ │ │ │ -82EA4 PAYLOAD │ │ │ │ - │ │ │ │ -83C3A LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ -83C3E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -83C3F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -83C40 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -83C42 Compression Method 0008 (8) 'Deflated' │ │ │ │ -83C44 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -83C48 CRC B429472D (3022604077) │ │ │ │ -83C4C Compressed Size 000001E1 (481) │ │ │ │ -83C50 Uncompressed Size 00000323 (803) │ │ │ │ -83C54 Filename Length 0011 (17) │ │ │ │ -83C56 Extra Length 001C (28) │ │ │ │ -83C58 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x83C58: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -83C69 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -83C6B Length 0009 (9) │ │ │ │ -83C6D Flags 03 (3) 'Modification Access' │ │ │ │ -83C6E Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -83C72 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -83C76 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -83C78 Length 000B (11) │ │ │ │ -83C7A Version 01 (1) │ │ │ │ -83C7B UID Size 04 (4) │ │ │ │ -83C7C UID 00000000 (0) │ │ │ │ -83C80 GID Size 04 (4) │ │ │ │ -83C81 GID 00000000 (0) │ │ │ │ -83C85 PAYLOAD │ │ │ │ - │ │ │ │ -83E66 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -83E6A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -83E6B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -83E6C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -83E6E Compression Method 0008 (8) 'Deflated' │ │ │ │ -83E70 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -83E74 CRC 1E45F0E3 (507900131) │ │ │ │ -83E78 Compressed Size 000006C1 (1729) │ │ │ │ -83E7C Uncompressed Size 00001439 (5177) │ │ │ │ -83E80 Filename Length 0019 (25) │ │ │ │ -83E82 Extra Length 001C (28) │ │ │ │ -83E84 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x83E84: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -83E9D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -83E9F Length 0009 (9) │ │ │ │ -83EA1 Flags 03 (3) 'Modification Access' │ │ │ │ -83EA2 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -83EA6 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -83EAA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -83EAC Length 000B (11) │ │ │ │ -83EAE Version 01 (1) │ │ │ │ -83EAF UID Size 04 (4) │ │ │ │ -83EB0 UID 00000000 (0) │ │ │ │ -83EB4 GID Size 04 (4) │ │ │ │ -83EB5 GID 00000000 (0) │ │ │ │ -83EB9 PAYLOAD │ │ │ │ - │ │ │ │ -8457A LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -8457E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8457F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -84580 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -84582 Compression Method 0008 (8) 'Deflated' │ │ │ │ -84584 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -84588 CRC 31106466 (823157862) │ │ │ │ -8458C Compressed Size 00001B86 (7046) │ │ │ │ -84590 Uncompressed Size 00009F03 (40707) │ │ │ │ -84594 Filename Length 0018 (24) │ │ │ │ -84596 Extra Length 001C (28) │ │ │ │ -84598 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x84598: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -845B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -845B2 Length 0009 (9) │ │ │ │ -845B4 Flags 03 (3) 'Modification Access' │ │ │ │ -845B5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -845B9 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -845BD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -845BF Length 000B (11) │ │ │ │ -845C1 Version 01 (1) │ │ │ │ -845C2 UID Size 04 (4) │ │ │ │ -845C3 UID 00000000 (0) │ │ │ │ -845C7 GID Size 04 (4) │ │ │ │ -845C8 GID 00000000 (0) │ │ │ │ -845CC PAYLOAD │ │ │ │ - │ │ │ │ -86152 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -86156 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -86157 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -86158 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8615A Compression Method 0008 (8) 'Deflated' │ │ │ │ -8615C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -86160 CRC EAA681B2 (3936780722) │ │ │ │ -86164 Compressed Size 000016FB (5883) │ │ │ │ -86168 Uncompressed Size 00008AB6 (35510) │ │ │ │ -8616C Filename Length 0012 (18) │ │ │ │ -8616E Extra Length 001C (28) │ │ │ │ -86170 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x86170: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -86182 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -86184 Length 0009 (9) │ │ │ │ -86186 Flags 03 (3) 'Modification Access' │ │ │ │ -86187 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8618B Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8618F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -86191 Length 000B (11) │ │ │ │ -86193 Version 01 (1) │ │ │ │ -86194 UID Size 04 (4) │ │ │ │ -86195 UID 00000000 (0) │ │ │ │ -86199 GID Size 04 (4) │ │ │ │ -8619A GID 00000000 (0) │ │ │ │ -8619E PAYLOAD │ │ │ │ - │ │ │ │ -87899 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -8789D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8789E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8789F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -878A1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -878A3 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -878A7 CRC 2B5B6715 (727410453) │ │ │ │ -878AB Compressed Size 00001E12 (7698) │ │ │ │ -878AF Uncompressed Size 00008803 (34819) │ │ │ │ -878B3 Filename Length 0016 (22) │ │ │ │ -878B5 Extra Length 001C (28) │ │ │ │ -878B7 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x878B7: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -878CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -878CF Length 0009 (9) │ │ │ │ -878D1 Flags 03 (3) 'Modification Access' │ │ │ │ -878D2 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -878D6 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -878DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -878DC Length 000B (11) │ │ │ │ -878DE Version 01 (1) │ │ │ │ -878DF UID Size 04 (4) │ │ │ │ -878E0 UID 00000000 (0) │ │ │ │ -878E4 GID Size 04 (4) │ │ │ │ -878E5 GID 00000000 (0) │ │ │ │ -878E9 PAYLOAD │ │ │ │ - │ │ │ │ -896FB LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -896FF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -89700 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -89701 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -89703 Compression Method 0008 (8) 'Deflated' │ │ │ │ -89705 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -89709 CRC F1BBDD3F (4055620927) │ │ │ │ -8970D Compressed Size 000029A9 (10665) │ │ │ │ -89711 Uncompressed Size 0000D04F (53327) │ │ │ │ -89715 Filename Length 001A (26) │ │ │ │ -89717 Extra Length 001C (28) │ │ │ │ -89719 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x89719: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -89733 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -89735 Length 0009 (9) │ │ │ │ -89737 Flags 03 (3) 'Modification Access' │ │ │ │ -89738 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8973C Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -89740 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -89742 Length 000B (11) │ │ │ │ -89744 Version 01 (1) │ │ │ │ -89745 UID Size 04 (4) │ │ │ │ -89746 UID 00000000 (0) │ │ │ │ -8974A GID Size 04 (4) │ │ │ │ -8974B GID 00000000 (0) │ │ │ │ -8974F PAYLOAD │ │ │ │ - │ │ │ │ -8C0F8 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -8C0FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8C0FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8C0FE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8C100 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8C102 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -8C106 CRC DC838374 (3699606388) │ │ │ │ -8C10A Compressed Size 000009AB (2475) │ │ │ │ -8C10E Uncompressed Size 00001DB6 (7606) │ │ │ │ -8C112 Filename Length 0018 (24) │ │ │ │ -8C114 Extra Length 001C (28) │ │ │ │ -8C116 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8C116: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8C12E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8C130 Length 0009 (9) │ │ │ │ -8C132 Flags 03 (3) 'Modification Access' │ │ │ │ -8C133 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8C137 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8C13B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8C13D Length 000B (11) │ │ │ │ -8C13F Version 01 (1) │ │ │ │ -8C140 UID Size 04 (4) │ │ │ │ -8C141 UID 00000000 (0) │ │ │ │ -8C145 GID Size 04 (4) │ │ │ │ -8C146 GID 00000000 (0) │ │ │ │ -8C14A PAYLOAD │ │ │ │ - │ │ │ │ -8CAF5 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -8CAF9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8CAFA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8CAFB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8CAFD Compression Method 0008 (8) 'Deflated' │ │ │ │ -8CAFF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -8CB03 CRC F5E2129F (4125233823) │ │ │ │ -8CB07 Compressed Size 000016BC (5820) │ │ │ │ -8CB0B Uncompressed Size 000016CD (5837) │ │ │ │ -8CB0F Filename Length 0015 (21) │ │ │ │ -8CB11 Extra Length 001C (28) │ │ │ │ -8CB13 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8CB13: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8CB28 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8CB2A Length 0009 (9) │ │ │ │ -8CB2C Flags 03 (3) 'Modification Access' │ │ │ │ -8CB2D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8CB31 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8CB35 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8CB37 Length 000B (11) │ │ │ │ -8CB39 Version 01 (1) │ │ │ │ -8CB3A UID Size 04 (4) │ │ │ │ -8CB3B UID 00000000 (0) │ │ │ │ -8CB3F GID Size 04 (4) │ │ │ │ -8CB40 GID 00000000 (0) │ │ │ │ -8CB44 PAYLOAD │ │ │ │ - │ │ │ │ -8E200 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -8E204 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8E205 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8E206 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8E208 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8E20A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -8E20E CRC F5E2129F (4125233823) │ │ │ │ -8E212 Compressed Size 000016BC (5820) │ │ │ │ -8E216 Uncompressed Size 000016CD (5837) │ │ │ │ -8E21A Filename Length 001C (28) │ │ │ │ -8E21C Extra Length 001C (28) │ │ │ │ -8E21E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8E21E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8E23A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8E23C Length 0009 (9) │ │ │ │ -8E23E Flags 03 (3) 'Modification Access' │ │ │ │ -8E23F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8E243 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8E247 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8E249 Length 000B (11) │ │ │ │ -8E24B Version 01 (1) │ │ │ │ -8E24C UID Size 04 (4) │ │ │ │ -8E24D UID 00000000 (0) │ │ │ │ -8E251 GID Size 04 (4) │ │ │ │ -8E252 GID 00000000 (0) │ │ │ │ -8E256 PAYLOAD │ │ │ │ - │ │ │ │ -8F912 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -8F916 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -8F917 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8F918 General Purpose Flag 0000 (0) │ │ │ │ -8F91A Compression Method 0000 (0) 'Stored' │ │ │ │ -8F91C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -8F920 CRC FC95F24B (4237685323) │ │ │ │ -8F924 Compressed Size 00001B84 (7044) │ │ │ │ -8F928 Uncompressed Size 00001B84 (7044) │ │ │ │ -8F92C Filename Length 0016 (22) │ │ │ │ -8F92E Extra Length 001C (28) │ │ │ │ -8F930 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8F930: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8F946 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8F948 Length 0009 (9) │ │ │ │ -8F94A Flags 03 (3) 'Modification Access' │ │ │ │ -8F94B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8F94F Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -8F953 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8F955 Length 000B (11) │ │ │ │ -8F957 Version 01 (1) │ │ │ │ -8F958 UID Size 04 (4) │ │ │ │ -8F959 UID 00000000 (0) │ │ │ │ -8F95D GID Size 04 (4) │ │ │ │ -8F95E GID 00000000 (0) │ │ │ │ -8F962 PAYLOAD │ │ │ │ - │ │ │ │ -914E6 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -914EA Extract Zip Spec 0A (10) '1.0' │ │ │ │ -914EB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -914EC General Purpose Flag 0000 (0) │ │ │ │ -914EE Compression Method 0000 (0) 'Stored' │ │ │ │ -914F0 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -914F4 CRC D0D71F86 (3503759238) │ │ │ │ -914F8 Compressed Size 00000B7B (2939) │ │ │ │ -914FC Uncompressed Size 00000B7B (2939) │ │ │ │ -91500 Filename Length 0016 (22) │ │ │ │ -91502 Extra Length 001C (28) │ │ │ │ -91504 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x91504: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9151A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9151C Length 0009 (9) │ │ │ │ -9151E Flags 03 (3) 'Modification Access' │ │ │ │ -9151F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -91523 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -91527 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -91529 Length 000B (11) │ │ │ │ -9152B Version 01 (1) │ │ │ │ -9152C UID Size 04 (4) │ │ │ │ -9152D UID 00000000 (0) │ │ │ │ -91531 GID Size 04 (4) │ │ │ │ -91532 GID 00000000 (0) │ │ │ │ -91536 PAYLOAD │ │ │ │ - │ │ │ │ -920B1 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -920B5 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -920B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -920B7 General Purpose Flag 0000 (0) │ │ │ │ -920B9 Compression Method 0000 (0) 'Stored' │ │ │ │ -920BB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -920BF CRC FFF9C4D2 (4294558930) │ │ │ │ -920C3 Compressed Size 0000138F (5007) │ │ │ │ -920C7 Uncompressed Size 0000138F (5007) │ │ │ │ -920CB Filename Length 0016 (22) │ │ │ │ -920CD Extra Length 001C (28) │ │ │ │ -920CF Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x920CF: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -920E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -920E7 Length 0009 (9) │ │ │ │ -920E9 Flags 03 (3) 'Modification Access' │ │ │ │ -920EA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -920EE Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -920F2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -920F4 Length 000B (11) │ │ │ │ -920F6 Version 01 (1) │ │ │ │ -920F7 UID Size 04 (4) │ │ │ │ -920F8 UID 00000000 (0) │ │ │ │ -920FC GID Size 04 (4) │ │ │ │ -920FD GID 00000000 (0) │ │ │ │ -92101 PAYLOAD │ │ │ │ - │ │ │ │ -93490 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -93494 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -93495 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -93496 General Purpose Flag 0000 (0) │ │ │ │ -93498 Compression Method 0000 (0) 'Stored' │ │ │ │ -9349A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9349E CRC A1037E8E (2701360782) │ │ │ │ -934A2 Compressed Size 0000145E (5214) │ │ │ │ -934A6 Uncompressed Size 0000145E (5214) │ │ │ │ -934AA Filename Length 0016 (22) │ │ │ │ -934AC Extra Length 001C (28) │ │ │ │ -934AE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x934AE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -934C4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -934C6 Length 0009 (9) │ │ │ │ -934C8 Flags 03 (3) 'Modification Access' │ │ │ │ -934C9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -934CD Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -934D1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -934D3 Length 000B (11) │ │ │ │ -934D5 Version 01 (1) │ │ │ │ -934D6 UID Size 04 (4) │ │ │ │ -934D7 UID 00000000 (0) │ │ │ │ -934DB GID Size 04 (4) │ │ │ │ -934DC GID 00000000 (0) │ │ │ │ -934E0 PAYLOAD │ │ │ │ - │ │ │ │ -9493E LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -94942 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -94943 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -94944 General Purpose Flag 0000 (0) │ │ │ │ -94946 Compression Method 0000 (0) 'Stored' │ │ │ │ -94948 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9494C CRC 5E9E64F1 (1587438833) │ │ │ │ -94950 Compressed Size 000008EC (2284) │ │ │ │ -94954 Uncompressed Size 000008EC (2284) │ │ │ │ -94958 Filename Length 0016 (22) │ │ │ │ -9495A Extra Length 001C (28) │ │ │ │ -9495C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9495C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -94972 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -94974 Length 0009 (9) │ │ │ │ -94976 Flags 03 (3) 'Modification Access' │ │ │ │ -94977 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9497B Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9497F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -94981 Length 000B (11) │ │ │ │ -94983 Version 01 (1) │ │ │ │ -94984 UID Size 04 (4) │ │ │ │ -94985 UID 00000000 (0) │ │ │ │ -94989 GID Size 04 (4) │ │ │ │ -9498A GID 00000000 (0) │ │ │ │ -9498E PAYLOAD │ │ │ │ - │ │ │ │ -9527A LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -9527E Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9527F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -95280 General Purpose Flag 0000 (0) │ │ │ │ -95282 Compression Method 0000 (0) 'Stored' │ │ │ │ -95284 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -95288 CRC 42E340AB (1122189483) │ │ │ │ -9528C Compressed Size 00001F2E (7982) │ │ │ │ -95290 Uncompressed Size 00001F2E (7982) │ │ │ │ -95294 Filename Length 001E (30) │ │ │ │ -95296 Extra Length 001C (28) │ │ │ │ -95298 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x95298: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -952B6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -952B8 Length 0009 (9) │ │ │ │ -952BA Flags 03 (3) 'Modification Access' │ │ │ │ -952BB Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -952BF Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -952C3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -952C5 Length 000B (11) │ │ │ │ -952C7 Version 01 (1) │ │ │ │ -952C8 UID Size 04 (4) │ │ │ │ -952C9 UID 00000000 (0) │ │ │ │ -952CD GID Size 04 (4) │ │ │ │ -952CE GID 00000000 (0) │ │ │ │ -952D2 PAYLOAD │ │ │ │ - │ │ │ │ -97200 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -97204 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -97205 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -97206 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -97208 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9720A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9720E CRC 46D4ADFC (1188343292) │ │ │ │ -97212 Compressed Size 00003D71 (15729) │ │ │ │ -97216 Uncompressed Size 00016649 (91721) │ │ │ │ -9721A Filename Length 001A (26) │ │ │ │ -9721C Extra Length 001C (28) │ │ │ │ -9721E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9721E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -97238 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9723A Length 0009 (9) │ │ │ │ -9723C Flags 03 (3) 'Modification Access' │ │ │ │ -9723D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -97241 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -97245 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -97247 Length 000B (11) │ │ │ │ -97249 Version 01 (1) │ │ │ │ -9724A UID Size 04 (4) │ │ │ │ -9724B UID 00000000 (0) │ │ │ │ -9724F GID Size 04 (4) │ │ │ │ -97250 GID 00000000 (0) │ │ │ │ -97254 PAYLOAD │ │ │ │ - │ │ │ │ -9AFC5 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -9AFC9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9AFCA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9AFCB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9AFCD Compression Method 0008 (8) 'Deflated' │ │ │ │ -9AFCF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9AFD3 CRC 228F80DD (579829981) │ │ │ │ -9AFD7 Compressed Size 000029BF (10687) │ │ │ │ -9AFDB Uncompressed Size 0000BA6A (47722) │ │ │ │ -9AFDF Filename Length 0018 (24) │ │ │ │ -9AFE1 Extra Length 001C (28) │ │ │ │ -9AFE3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9AFE3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9AFFB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9AFFD Length 0009 (9) │ │ │ │ -9AFFF Flags 03 (3) 'Modification Access' │ │ │ │ -9B000 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9B004 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9B008 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9B00A Length 000B (11) │ │ │ │ -9B00C Version 01 (1) │ │ │ │ -9B00D UID Size 04 (4) │ │ │ │ -9B00E UID 00000000 (0) │ │ │ │ -9B012 GID Size 04 (4) │ │ │ │ -9B013 GID 00000000 (0) │ │ │ │ -9B017 PAYLOAD │ │ │ │ - │ │ │ │ -9D9D6 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -9D9DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9D9DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9D9DC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9D9DE Compression Method 0008 (8) 'Deflated' │ │ │ │ -9D9E0 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9D9E4 CRC DCB3B516 (3702764822) │ │ │ │ -9D9E8 Compressed Size 000000AE (174) │ │ │ │ -9D9EC Uncompressed Size 000000FC (252) │ │ │ │ -9D9F0 Filename Length 0016 (22) │ │ │ │ -9D9F2 Extra Length 001C (28) │ │ │ │ -9D9F4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9D9F4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DA0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DA0C Length 0009 (9) │ │ │ │ -9DA0E Flags 03 (3) 'Modification Access' │ │ │ │ -9DA0F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DA13 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DA17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DA19 Length 000B (11) │ │ │ │ -9DA1B Version 01 (1) │ │ │ │ -9DA1C UID Size 04 (4) │ │ │ │ -9DA1D UID 00000000 (0) │ │ │ │ -9DA21 GID Size 04 (4) │ │ │ │ -9DA22 GID 00000000 (0) │ │ │ │ -9DA26 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +3C4BF LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +3C4C3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3C4C4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3C4C5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3C4C7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3C4C9 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +3C4CD CRC D1448544 (3510928708) │ │ │ │ +3C4D1 Compressed Size 000009A5 (2469) │ │ │ │ +3C4D5 Uncompressed Size 00001B64 (7012) │ │ │ │ +3C4D9 Filename Length 0010 (16) │ │ │ │ +3C4DB Extra Length 001C (28) │ │ │ │ +3C4DD Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3C4DD: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C4ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C4EF Length 0009 (9) │ │ │ │ +3C4F1 Flags 03 (3) 'Modification Access' │ │ │ │ +3C4F2 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3C4F6 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3C4FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C4FC Length 000B (11) │ │ │ │ +3C4FE Version 01 (1) │ │ │ │ +3C4FF UID Size 04 (4) │ │ │ │ +3C500 UID 00000000 (0) │ │ │ │ +3C504 GID Size 04 (4) │ │ │ │ +3C505 GID 00000000 (0) │ │ │ │ +3C509 PAYLOAD │ │ │ │ + │ │ │ │ +3CEAE LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +3CEB2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3CEB3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3CEB4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3CEB6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3CEB8 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +3CEBC CRC 3E5AEB62 (1046145890) │ │ │ │ +3CEC0 Compressed Size 000006B7 (1719) │ │ │ │ +3CEC4 Uncompressed Size 00001565 (5477) │ │ │ │ +3CEC8 Filename Length 0012 (18) │ │ │ │ +3CECA Extra Length 001C (28) │ │ │ │ +3CECC Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3CECC: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3CEDE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3CEE0 Length 0009 (9) │ │ │ │ +3CEE2 Flags 03 (3) 'Modification Access' │ │ │ │ +3CEE3 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3CEE7 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3CEEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3CEED Length 000B (11) │ │ │ │ +3CEEF Version 01 (1) │ │ │ │ +3CEF0 UID Size 04 (4) │ │ │ │ +3CEF1 UID 00000000 (0) │ │ │ │ +3CEF5 GID Size 04 (4) │ │ │ │ +3CEF6 GID 00000000 (0) │ │ │ │ +3CEFA PAYLOAD │ │ │ │ + │ │ │ │ +3D5B1 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +3D5B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3D5B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3D5B7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3D5B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3D5BB Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +3D5BF CRC 8FFD8ADC (2415758044) │ │ │ │ +3D5C3 Compressed Size 00002A18 (10776) │ │ │ │ +3D5C7 Uncompressed Size 0000B1C5 (45509) │ │ │ │ +3D5CB Filename Length 0010 (16) │ │ │ │ +3D5CD Extra Length 001C (28) │ │ │ │ +3D5CF Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3D5CF: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3D5DF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3D5E1 Length 0009 (9) │ │ │ │ +3D5E3 Flags 03 (3) 'Modification Access' │ │ │ │ +3D5E4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3D5E8 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +3D5EC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3D5EE Length 000B (11) │ │ │ │ +3D5F0 Version 01 (1) │ │ │ │ +3D5F1 UID Size 04 (4) │ │ │ │ +3D5F2 UID 00000000 (0) │ │ │ │ +3D5F6 GID Size 04 (4) │ │ │ │ +3D5F7 GID 00000000 (0) │ │ │ │ +3D5FB PAYLOAD │ │ │ │ + │ │ │ │ +40013 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +40017 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +40018 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +40019 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4001B Compression Method 0008 (8) 'Deflated' │ │ │ │ +4001D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +40021 CRC 62C4CC1C (1657064476) │ │ │ │ +40025 Compressed Size 00001E8A (7818) │ │ │ │ +40029 Uncompressed Size 00009AAA (39594) │ │ │ │ +4002D Filename Length 0012 (18) │ │ │ │ +4002F Extra Length 001C (28) │ │ │ │ +40031 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x40031: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40043 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +40045 Length 0009 (9) │ │ │ │ +40047 Flags 03 (3) 'Modification Access' │ │ │ │ +40048 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4004C Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +40050 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40052 Length 000B (11) │ │ │ │ +40054 Version 01 (1) │ │ │ │ +40055 UID Size 04 (4) │ │ │ │ +40056 UID 00000000 (0) │ │ │ │ +4005A GID Size 04 (4) │ │ │ │ +4005B GID 00000000 (0) │ │ │ │ +4005F PAYLOAD │ │ │ │ + │ │ │ │ +41EE9 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +41EED Extract Zip Spec 14 (20) '2.0' │ │ │ │ +41EEE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +41EEF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +41EF1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +41EF3 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +41EF7 CRC 2B1DF12F (723382575) │ │ │ │ +41EFB Compressed Size 0000147C (5244) │ │ │ │ +41EFF Uncompressed Size 00007ACF (31439) │ │ │ │ +41F03 Filename Length 0018 (24) │ │ │ │ +41F05 Extra Length 001C (28) │ │ │ │ +41F07 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x41F07: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +41F1F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +41F21 Length 0009 (9) │ │ │ │ +41F23 Flags 03 (3) 'Modification Access' │ │ │ │ +41F24 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +41F28 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +41F2C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +41F2E Length 000B (11) │ │ │ │ +41F30 Version 01 (1) │ │ │ │ +41F31 UID Size 04 (4) │ │ │ │ +41F32 UID 00000000 (0) │ │ │ │ +41F36 GID Size 04 (4) │ │ │ │ +41F37 GID 00000000 (0) │ │ │ │ +41F3B PAYLOAD │ │ │ │ + │ │ │ │ +433B7 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +433BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +433BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +433BD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +433BF Compression Method 0008 (8) 'Deflated' │ │ │ │ +433C1 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +433C5 CRC 0BA86E64 (195587684) │ │ │ │ +433C9 Compressed Size 000018D4 (6356) │ │ │ │ +433CD Uncompressed Size 0000A7F4 (42996) │ │ │ │ +433D1 Filename Length 001F (31) │ │ │ │ +433D3 Extra Length 001C (28) │ │ │ │ +433D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x433D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +433F4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +433F6 Length 0009 (9) │ │ │ │ +433F8 Flags 03 (3) 'Modification Access' │ │ │ │ +433F9 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +433FD Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +43401 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +43403 Length 000B (11) │ │ │ │ +43405 Version 01 (1) │ │ │ │ +43406 UID Size 04 (4) │ │ │ │ +43407 UID 00000000 (0) │ │ │ │ +4340B GID Size 04 (4) │ │ │ │ +4340C GID 00000000 (0) │ │ │ │ +43410 PAYLOAD │ │ │ │ + │ │ │ │ +44CE4 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +44CE8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +44CE9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +44CEA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +44CEC Compression Method 0008 (8) 'Deflated' │ │ │ │ +44CEE Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +44CF2 CRC FFAF198F (4289665423) │ │ │ │ +44CF6 Compressed Size 000003F6 (1014) │ │ │ │ +44CFA Uncompressed Size 000008A3 (2211) │ │ │ │ +44CFE Filename Length 001E (30) │ │ │ │ +44D00 Extra Length 001C (28) │ │ │ │ +44D02 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x44D02: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +44D20 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +44D22 Length 0009 (9) │ │ │ │ +44D24 Flags 03 (3) 'Modification Access' │ │ │ │ +44D25 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +44D29 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +44D2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +44D2F Length 000B (11) │ │ │ │ +44D31 Version 01 (1) │ │ │ │ +44D32 UID Size 04 (4) │ │ │ │ +44D33 UID 00000000 (0) │ │ │ │ +44D37 GID Size 04 (4) │ │ │ │ +44D38 GID 00000000 (0) │ │ │ │ +44D3C PAYLOAD │ │ │ │ + │ │ │ │ +45132 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +45136 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +45137 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +45138 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4513A Compression Method 0008 (8) 'Deflated' │ │ │ │ +4513C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +45140 CRC 32E3FA13 (853801491) │ │ │ │ +45144 Compressed Size 00004292 (17042) │ │ │ │ +45148 Uncompressed Size 0000D8DC (55516) │ │ │ │ +4514C Filename Length 0013 (19) │ │ │ │ +4514E Extra Length 001C (28) │ │ │ │ +45150 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x45150: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +45163 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45165 Length 0009 (9) │ │ │ │ +45167 Flags 03 (3) 'Modification Access' │ │ │ │ +45168 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4516C Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +45170 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +45172 Length 000B (11) │ │ │ │ +45174 Version 01 (1) │ │ │ │ +45175 UID Size 04 (4) │ │ │ │ +45176 UID 00000000 (0) │ │ │ │ +4517A GID Size 04 (4) │ │ │ │ +4517B GID 00000000 (0) │ │ │ │ +4517F PAYLOAD │ │ │ │ + │ │ │ │ +49411 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +49415 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +49416 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +49417 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +49419 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4941B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +4941F CRC 71753995 (1903507861) │ │ │ │ +49423 Compressed Size 000026C4 (9924) │ │ │ │ +49427 Uncompressed Size 00006E45 (28229) │ │ │ │ +4942B Filename Length 0019 (25) │ │ │ │ +4942D Extra Length 001C (28) │ │ │ │ +4942F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4942F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +49448 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4944A Length 0009 (9) │ │ │ │ +4944C Flags 03 (3) 'Modification Access' │ │ │ │ +4944D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +49451 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +49455 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +49457 Length 000B (11) │ │ │ │ +49459 Version 01 (1) │ │ │ │ +4945A UID Size 04 (4) │ │ │ │ +4945B UID 00000000 (0) │ │ │ │ +4945F GID Size 04 (4) │ │ │ │ +49460 GID 00000000 (0) │ │ │ │ +49464 PAYLOAD │ │ │ │ + │ │ │ │ +4BB28 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +4BB2C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4BB2D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4BB2E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4BB30 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4BB32 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +4BB36 CRC 0369577C (57235324) │ │ │ │ +4BB3A Compressed Size 00002739 (10041) │ │ │ │ +4BB3E Uncompressed Size 00008B83 (35715) │ │ │ │ +4BB42 Filename Length 0019 (25) │ │ │ │ +4BB44 Extra Length 001C (28) │ │ │ │ +4BB46 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4BB46: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4BB5F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4BB61 Length 0009 (9) │ │ │ │ +4BB63 Flags 03 (3) 'Modification Access' │ │ │ │ +4BB64 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4BB68 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4BB6C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4BB6E Length 000B (11) │ │ │ │ +4BB70 Version 01 (1) │ │ │ │ +4BB71 UID Size 04 (4) │ │ │ │ +4BB72 UID 00000000 (0) │ │ │ │ +4BB76 GID Size 04 (4) │ │ │ │ +4BB77 GID 00000000 (0) │ │ │ │ +4BB7B PAYLOAD │ │ │ │ + │ │ │ │ +4E2B4 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +4E2B8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4E2B9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4E2BA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4E2BC Compression Method 0008 (8) 'Deflated' │ │ │ │ +4E2BE Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +4E2C2 CRC A05E06F6 (2690516726) │ │ │ │ +4E2C6 Compressed Size 00000CF1 (3313) │ │ │ │ +4E2CA Uncompressed Size 0000517A (20858) │ │ │ │ +4E2CE Filename Length 0021 (33) │ │ │ │ +4E2D0 Extra Length 001C (28) │ │ │ │ +4E2D2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4E2D2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4E2F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4E2F5 Length 0009 (9) │ │ │ │ +4E2F7 Flags 03 (3) 'Modification Access' │ │ │ │ +4E2F8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4E2FC Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4E300 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4E302 Length 000B (11) │ │ │ │ +4E304 Version 01 (1) │ │ │ │ +4E305 UID Size 04 (4) │ │ │ │ +4E306 UID 00000000 (0) │ │ │ │ +4E30A GID Size 04 (4) │ │ │ │ +4E30B GID 00000000 (0) │ │ │ │ +4E30F PAYLOAD │ │ │ │ + │ │ │ │ +4F000 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +4F004 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F005 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F006 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F008 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F00A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +4F00E CRC 8F01E69C (2399266460) │ │ │ │ +4F012 Compressed Size 00000468 (1128) │ │ │ │ +4F016 Uncompressed Size 00000931 (2353) │ │ │ │ +4F01A Filename Length 001B (27) │ │ │ │ +4F01C Extra Length 001C (28) │ │ │ │ +4F01E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F01E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F039 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F03B Length 0009 (9) │ │ │ │ +4F03D Flags 03 (3) 'Modification Access' │ │ │ │ +4F03E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4F042 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4F046 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F048 Length 000B (11) │ │ │ │ +4F04A Version 01 (1) │ │ │ │ +4F04B UID Size 04 (4) │ │ │ │ +4F04C UID 00000000 (0) │ │ │ │ +4F050 GID Size 04 (4) │ │ │ │ +4F051 GID 00000000 (0) │ │ │ │ +4F055 PAYLOAD │ │ │ │ + │ │ │ │ +4F4BD LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +4F4C1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F4C2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F4C3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F4C5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F4C7 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +4F4CB CRC 26F2BE77 (653442679) │ │ │ │ +4F4CF Compressed Size 000016F0 (5872) │ │ │ │ +4F4D3 Uncompressed Size 00007A6D (31341) │ │ │ │ +4F4D7 Filename Length 001F (31) │ │ │ │ +4F4D9 Extra Length 001C (28) │ │ │ │ +4F4DB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F4DB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F4FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F4FC Length 0009 (9) │ │ │ │ +4F4FE Flags 03 (3) 'Modification Access' │ │ │ │ +4F4FF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4F503 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +4F507 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F509 Length 000B (11) │ │ │ │ +4F50B Version 01 (1) │ │ │ │ +4F50C UID Size 04 (4) │ │ │ │ +4F50D UID 00000000 (0) │ │ │ │ +4F511 GID Size 04 (4) │ │ │ │ +4F512 GID 00000000 (0) │ │ │ │ +4F516 PAYLOAD │ │ │ │ + │ │ │ │ +50C06 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +50C0A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +50C0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +50C0C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +50C0E Compression Method 0008 (8) 'Deflated' │ │ │ │ +50C10 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +50C14 CRC 11C5667D (298149501) │ │ │ │ +50C18 Compressed Size 00004166 (16742) │ │ │ │ +50C1C Uncompressed Size 0001CF93 (118675) │ │ │ │ +50C20 Filename Length 0010 (16) │ │ │ │ +50C22 Extra Length 001C (28) │ │ │ │ +50C24 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x50C24: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +50C34 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +50C36 Length 0009 (9) │ │ │ │ +50C38 Flags 03 (3) 'Modification Access' │ │ │ │ +50C39 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +50C3D Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +50C41 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +50C43 Length 000B (11) │ │ │ │ +50C45 Version 01 (1) │ │ │ │ +50C46 UID Size 04 (4) │ │ │ │ +50C47 UID 00000000 (0) │ │ │ │ +50C4B GID Size 04 (4) │ │ │ │ +50C4C GID 00000000 (0) │ │ │ │ +50C50 PAYLOAD │ │ │ │ + │ │ │ │ +54DB6 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +54DBA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +54DBB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +54DBC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +54DBE Compression Method 0008 (8) 'Deflated' │ │ │ │ +54DC0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +54DC4 CRC 2020DAF6 (539024118) │ │ │ │ +54DC8 Compressed Size 00000A94 (2708) │ │ │ │ +54DCC Uncompressed Size 00002105 (8453) │ │ │ │ +54DD0 Filename Length 0014 (20) │ │ │ │ +54DD2 Extra Length 001C (28) │ │ │ │ +54DD4 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x54DD4: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +54DE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +54DEA Length 0009 (9) │ │ │ │ +54DEC Flags 03 (3) 'Modification Access' │ │ │ │ +54DED Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +54DF1 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +54DF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +54DF7 Length 000B (11) │ │ │ │ +54DF9 Version 01 (1) │ │ │ │ +54DFA UID Size 04 (4) │ │ │ │ +54DFB UID 00000000 (0) │ │ │ │ +54DFF GID Size 04 (4) │ │ │ │ +54E00 GID 00000000 (0) │ │ │ │ +54E04 PAYLOAD │ │ │ │ + │ │ │ │ +55898 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +5589C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5589D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5589E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +558A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +558A2 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +558A6 CRC 1EA2D694 (513988244) │ │ │ │ +558AA Compressed Size 0000AC9E (44190) │ │ │ │ +558AE Uncompressed Size 0003E418 (255000) │ │ │ │ +558B2 Filename Length 0017 (23) │ │ │ │ +558B4 Extra Length 001C (28) │ │ │ │ +558B6 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x558B6: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +558CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +558CF Length 0009 (9) │ │ │ │ +558D1 Flags 03 (3) 'Modification Access' │ │ │ │ +558D2 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +558D6 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +558DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +558DC Length 000B (11) │ │ │ │ +558DE Version 01 (1) │ │ │ │ +558DF UID Size 04 (4) │ │ │ │ +558E0 UID 00000000 (0) │ │ │ │ +558E4 GID Size 04 (4) │ │ │ │ +558E5 GID 00000000 (0) │ │ │ │ +558E9 PAYLOAD │ │ │ │ + │ │ │ │ +60587 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +6058B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6058C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6058D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6058F Compression Method 0008 (8) 'Deflated' │ │ │ │ +60591 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +60595 CRC F3542EE5 (4082380517) │ │ │ │ +60599 Compressed Size 00000401 (1025) │ │ │ │ +6059D Uncompressed Size 0000093D (2365) │ │ │ │ +605A1 Filename Length 0013 (19) │ │ │ │ +605A3 Extra Length 001C (28) │ │ │ │ +605A5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x605A5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +605B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +605BA Length 0009 (9) │ │ │ │ +605BC Flags 03 (3) 'Modification Access' │ │ │ │ +605BD Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +605C1 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +605C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +605C7 Length 000B (11) │ │ │ │ +605C9 Version 01 (1) │ │ │ │ +605CA UID Size 04 (4) │ │ │ │ +605CB UID 00000000 (0) │ │ │ │ +605CF GID Size 04 (4) │ │ │ │ +605D0 GID 00000000 (0) │ │ │ │ +605D4 PAYLOAD │ │ │ │ + │ │ │ │ +609D5 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +609D9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +609DA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +609DB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +609DD Compression Method 0008 (8) 'Deflated' │ │ │ │ +609DF Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +609E3 CRC 242FEA37 (607119927) │ │ │ │ +609E7 Compressed Size 000014E5 (5349) │ │ │ │ +609EB Uncompressed Size 0000687B (26747) │ │ │ │ +609EF Filename Length 0012 (18) │ │ │ │ +609F1 Extra Length 001C (28) │ │ │ │ +609F3 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x609F3: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +60A05 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +60A07 Length 0009 (9) │ │ │ │ +60A09 Flags 03 (3) 'Modification Access' │ │ │ │ +60A0A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +60A0E Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +60A12 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +60A14 Length 000B (11) │ │ │ │ +60A16 Version 01 (1) │ │ │ │ +60A17 UID Size 04 (4) │ │ │ │ +60A18 UID 00000000 (0) │ │ │ │ +60A1C GID Size 04 (4) │ │ │ │ +60A1D GID 00000000 (0) │ │ │ │ +60A21 PAYLOAD │ │ │ │ + │ │ │ │ +61F06 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +61F0A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +61F0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +61F0C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +61F0E Compression Method 0008 (8) 'Deflated' │ │ │ │ +61F10 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +61F14 CRC A41464A8 (2752799912) │ │ │ │ +61F18 Compressed Size 000011EB (4587) │ │ │ │ +61F1C Uncompressed Size 000040F5 (16629) │ │ │ │ +61F20 Filename Length 0012 (18) │ │ │ │ +61F22 Extra Length 001C (28) │ │ │ │ +61F24 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x61F24: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +61F36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +61F38 Length 0009 (9) │ │ │ │ +61F3A Flags 03 (3) 'Modification Access' │ │ │ │ +61F3B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +61F3F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +61F43 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +61F45 Length 000B (11) │ │ │ │ +61F47 Version 01 (1) │ │ │ │ +61F48 UID Size 04 (4) │ │ │ │ +61F49 UID 00000000 (0) │ │ │ │ +61F4D GID Size 04 (4) │ │ │ │ +61F4E GID 00000000 (0) │ │ │ │ +61F52 PAYLOAD │ │ │ │ + │ │ │ │ +6313D LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +63141 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +63142 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +63143 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +63145 Compression Method 0008 (8) 'Deflated' │ │ │ │ +63147 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6314B CRC 39168DC1 (957779393) │ │ │ │ +6314F Compressed Size 000009DA (2522) │ │ │ │ +63153 Uncompressed Size 00003529 (13609) │ │ │ │ +63157 Filename Length 0019 (25) │ │ │ │ +63159 Extra Length 001C (28) │ │ │ │ +6315B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6315B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63174 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +63176 Length 0009 (9) │ │ │ │ +63178 Flags 03 (3) 'Modification Access' │ │ │ │ +63179 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6317D Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +63181 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +63183 Length 000B (11) │ │ │ │ +63185 Version 01 (1) │ │ │ │ +63186 UID Size 04 (4) │ │ │ │ +63187 UID 00000000 (0) │ │ │ │ +6318B GID Size 04 (4) │ │ │ │ +6318C GID 00000000 (0) │ │ │ │ +63190 PAYLOAD │ │ │ │ + │ │ │ │ +63B6A LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +63B6E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +63B6F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +63B70 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +63B72 Compression Method 0008 (8) 'Deflated' │ │ │ │ +63B74 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +63B78 CRC 65AF560D (1705989645) │ │ │ │ +63B7C Compressed Size 000018AD (6317) │ │ │ │ +63B80 Uncompressed Size 0000A605 (42501) │ │ │ │ +63B84 Filename Length 0019 (25) │ │ │ │ +63B86 Extra Length 001C (28) │ │ │ │ +63B88 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x63B88: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63BA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +63BA3 Length 0009 (9) │ │ │ │ +63BA5 Flags 03 (3) 'Modification Access' │ │ │ │ +63BA6 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +63BAA Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +63BAE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +63BB0 Length 000B (11) │ │ │ │ +63BB2 Version 01 (1) │ │ │ │ +63BB3 UID Size 04 (4) │ │ │ │ +63BB4 UID 00000000 (0) │ │ │ │ +63BB8 GID Size 04 (4) │ │ │ │ +63BB9 GID 00000000 (0) │ │ │ │ +63BBD PAYLOAD │ │ │ │ + │ │ │ │ +6546A LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +6546E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6546F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +65470 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +65472 Compression Method 0008 (8) 'Deflated' │ │ │ │ +65474 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +65478 CRC 47E0D6DC (1205917404) │ │ │ │ +6547C Compressed Size 0000177E (6014) │ │ │ │ +65480 Uncompressed Size 0000472C (18220) │ │ │ │ +65484 Filename Length 0014 (20) │ │ │ │ +65486 Extra Length 001C (28) │ │ │ │ +65488 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x65488: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6549C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6549E Length 0009 (9) │ │ │ │ +654A0 Flags 03 (3) 'Modification Access' │ │ │ │ +654A1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +654A5 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +654A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +654AB Length 000B (11) │ │ │ │ +654AD Version 01 (1) │ │ │ │ +654AE UID Size 04 (4) │ │ │ │ +654AF UID 00000000 (0) │ │ │ │ +654B3 GID Size 04 (4) │ │ │ │ +654B4 GID 00000000 (0) │ │ │ │ +654B8 PAYLOAD │ │ │ │ + │ │ │ │ +66C36 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +66C3A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +66C3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +66C3C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +66C3E Compression Method 0008 (8) 'Deflated' │ │ │ │ +66C40 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +66C44 CRC EDF51269 (3992261225) │ │ │ │ +66C48 Compressed Size 0000040B (1035) │ │ │ │ +66C4C Uncompressed Size 00000825 (2085) │ │ │ │ +66C50 Filename Length 001C (28) │ │ │ │ +66C52 Extra Length 001C (28) │ │ │ │ +66C54 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x66C54: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +66C70 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +66C72 Length 0009 (9) │ │ │ │ +66C74 Flags 03 (3) 'Modification Access' │ │ │ │ +66C75 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +66C79 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +66C7D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +66C7F Length 000B (11) │ │ │ │ +66C81 Version 01 (1) │ │ │ │ +66C82 UID Size 04 (4) │ │ │ │ +66C83 UID 00000000 (0) │ │ │ │ +66C87 GID Size 04 (4) │ │ │ │ +66C88 GID 00000000 (0) │ │ │ │ +66C8C PAYLOAD │ │ │ │ + │ │ │ │ +67097 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +6709B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6709C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6709D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6709F Compression Method 0008 (8) 'Deflated' │ │ │ │ +670A1 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +670A5 CRC 2BC65F52 (734420818) │ │ │ │ +670A9 Compressed Size 0000247D (9341) │ │ │ │ +670AD Uncompressed Size 0000B56F (46447) │ │ │ │ +670B1 Filename Length 001F (31) │ │ │ │ +670B3 Extra Length 001C (28) │ │ │ │ +670B5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x670B5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +670D4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +670D6 Length 0009 (9) │ │ │ │ +670D8 Flags 03 (3) 'Modification Access' │ │ │ │ +670D9 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +670DD Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +670E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +670E3 Length 000B (11) │ │ │ │ +670E5 Version 01 (1) │ │ │ │ +670E6 UID Size 04 (4) │ │ │ │ +670E7 UID 00000000 (0) │ │ │ │ +670EB GID Size 04 (4) │ │ │ │ +670EC GID 00000000 (0) │ │ │ │ +670F0 PAYLOAD │ │ │ │ + │ │ │ │ +6956D LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +69571 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +69572 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +69573 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +69575 Compression Method 0008 (8) 'Deflated' │ │ │ │ +69577 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6957B CRC 0B9BF55C (194770268) │ │ │ │ +6957F Compressed Size 00000E7C (3708) │ │ │ │ +69583 Uncompressed Size 000052D9 (21209) │ │ │ │ +69587 Filename Length 001F (31) │ │ │ │ +69589 Extra Length 001C (28) │ │ │ │ +6958B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6958B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +695AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +695AC Length 0009 (9) │ │ │ │ +695AE Flags 03 (3) 'Modification Access' │ │ │ │ +695AF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +695B3 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +695B7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +695B9 Length 000B (11) │ │ │ │ +695BB Version 01 (1) │ │ │ │ +695BC UID Size 04 (4) │ │ │ │ +695BD UID 00000000 (0) │ │ │ │ +695C1 GID Size 04 (4) │ │ │ │ +695C2 GID 00000000 (0) │ │ │ │ +695C6 PAYLOAD │ │ │ │ + │ │ │ │ +6A442 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +6A446 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6A447 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6A448 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6A44A Compression Method 0008 (8) 'Deflated' │ │ │ │ +6A44C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6A450 CRC 535A7770 (1398437744) │ │ │ │ +6A454 Compressed Size 00000A44 (2628) │ │ │ │ +6A458 Uncompressed Size 0000247A (9338) │ │ │ │ +6A45C Filename Length 0013 (19) │ │ │ │ +6A45E Extra Length 001C (28) │ │ │ │ +6A460 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6A460: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6A473 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6A475 Length 0009 (9) │ │ │ │ +6A477 Flags 03 (3) 'Modification Access' │ │ │ │ +6A478 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6A47C Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6A480 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6A482 Length 000B (11) │ │ │ │ +6A484 Version 01 (1) │ │ │ │ +6A485 UID Size 04 (4) │ │ │ │ +6A486 UID 00000000 (0) │ │ │ │ +6A48A GID Size 04 (4) │ │ │ │ +6A48B GID 00000000 (0) │ │ │ │ +6A48F PAYLOAD │ │ │ │ + │ │ │ │ +6AED3 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +6AED7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6AED8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6AED9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6AEDB Compression Method 0008 (8) 'Deflated' │ │ │ │ +6AEDD Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6AEE1 CRC FDE5E6FA (4259702522) │ │ │ │ +6AEE5 Compressed Size 00002484 (9348) │ │ │ │ +6AEE9 Uncompressed Size 0000B84C (47180) │ │ │ │ +6AEED Filename Length 0019 (25) │ │ │ │ +6AEEF Extra Length 001C (28) │ │ │ │ +6AEF1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6AEF1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6AF0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6AF0C Length 0009 (9) │ │ │ │ +6AF0E Flags 03 (3) 'Modification Access' │ │ │ │ +6AF0F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6AF13 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6AF17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6AF19 Length 000B (11) │ │ │ │ +6AF1B Version 01 (1) │ │ │ │ +6AF1C UID Size 04 (4) │ │ │ │ +6AF1D UID 00000000 (0) │ │ │ │ +6AF21 GID Size 04 (4) │ │ │ │ +6AF22 GID 00000000 (0) │ │ │ │ +6AF26 PAYLOAD │ │ │ │ + │ │ │ │ +6D3AA LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +6D3AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6D3AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6D3B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6D3B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6D3B4 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6D3B8 CRC 645CB06A (1683796074) │ │ │ │ +6D3BC Compressed Size 00000EF9 (3833) │ │ │ │ +6D3C0 Uncompressed Size 00003A2C (14892) │ │ │ │ +6D3C4 Filename Length 0024 (36) │ │ │ │ +6D3C6 Extra Length 001C (28) │ │ │ │ +6D3C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6D3C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6D3EC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6D3EE Length 0009 (9) │ │ │ │ +6D3F0 Flags 03 (3) 'Modification Access' │ │ │ │ +6D3F1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6D3F5 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6D3F9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6D3FB Length 000B (11) │ │ │ │ +6D3FD Version 01 (1) │ │ │ │ +6D3FE UID Size 04 (4) │ │ │ │ +6D3FF UID 00000000 (0) │ │ │ │ +6D403 GID Size 04 (4) │ │ │ │ +6D404 GID 00000000 (0) │ │ │ │ +6D408 PAYLOAD │ │ │ │ + │ │ │ │ +6E301 LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +6E305 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6E306 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6E307 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6E309 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6E30B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6E30F CRC 24DED018 (618582040) │ │ │ │ +6E313 Compressed Size 00001AC1 (6849) │ │ │ │ +6E317 Uncompressed Size 00005EDC (24284) │ │ │ │ +6E31B Filename Length 0017 (23) │ │ │ │ +6E31D Extra Length 001C (28) │ │ │ │ +6E31F Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6E31F: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6E336 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6E338 Length 0009 (9) │ │ │ │ +6E33A Flags 03 (3) 'Modification Access' │ │ │ │ +6E33B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6E33F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6E343 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6E345 Length 000B (11) │ │ │ │ +6E347 Version 01 (1) │ │ │ │ +6E348 UID Size 04 (4) │ │ │ │ +6E349 UID 00000000 (0) │ │ │ │ +6E34D GID Size 04 (4) │ │ │ │ +6E34E GID 00000000 (0) │ │ │ │ +6E352 PAYLOAD │ │ │ │ + │ │ │ │ +6FE13 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +6FE17 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6FE18 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6FE19 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6FE1B Compression Method 0008 (8) 'Deflated' │ │ │ │ +6FE1D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +6FE21 CRC 11E32AF1 (300100337) │ │ │ │ +6FE25 Compressed Size 00000ED3 (3795) │ │ │ │ +6FE29 Uncompressed Size 000038E2 (14562) │ │ │ │ +6FE2D Filename Length 0023 (35) │ │ │ │ +6FE2F Extra Length 001C (28) │ │ │ │ +6FE31 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6FE31: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6FE54 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6FE56 Length 0009 (9) │ │ │ │ +6FE58 Flags 03 (3) 'Modification Access' │ │ │ │ +6FE59 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6FE5D Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +6FE61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6FE63 Length 000B (11) │ │ │ │ +6FE65 Version 01 (1) │ │ │ │ +6FE66 UID Size 04 (4) │ │ │ │ +6FE67 UID 00000000 (0) │ │ │ │ +6FE6B GID Size 04 (4) │ │ │ │ +6FE6C GID 00000000 (0) │ │ │ │ +6FE70 PAYLOAD │ │ │ │ + │ │ │ │ +70D43 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +70D47 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +70D48 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +70D49 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +70D4B Compression Method 0008 (8) 'Deflated' │ │ │ │ +70D4D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +70D51 CRC 2DB7929F (767005343) │ │ │ │ +70D55 Compressed Size 00000113 (275) │ │ │ │ +70D59 Uncompressed Size 000001F3 (499) │ │ │ │ +70D5D Filename Length 001B (27) │ │ │ │ +70D5F Extra Length 001C (28) │ │ │ │ +70D61 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x70D61: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +70D7C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +70D7E Length 0009 (9) │ │ │ │ +70D80 Flags 03 (3) 'Modification Access' │ │ │ │ +70D81 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +70D85 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +70D89 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +70D8B Length 000B (11) │ │ │ │ +70D8D Version 01 (1) │ │ │ │ +70D8E UID Size 04 (4) │ │ │ │ +70D8F UID 00000000 (0) │ │ │ │ +70D93 GID Size 04 (4) │ │ │ │ +70D94 GID 00000000 (0) │ │ │ │ +70D98 PAYLOAD │ │ │ │ + │ │ │ │ +70EAB LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +70EAF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +70EB0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +70EB1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +70EB3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +70EB5 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +70EB9 CRC 62D11409 (1657869321) │ │ │ │ +70EBD Compressed Size 00001892 (6290) │ │ │ │ +70EC1 Uncompressed Size 00008FAC (36780) │ │ │ │ +70EC5 Filename Length 001D (29) │ │ │ │ +70EC7 Extra Length 001C (28) │ │ │ │ +70EC9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x70EC9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +70EE6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +70EE8 Length 0009 (9) │ │ │ │ +70EEA Flags 03 (3) 'Modification Access' │ │ │ │ +70EEB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +70EEF Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +70EF3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +70EF5 Length 000B (11) │ │ │ │ +70EF7 Version 01 (1) │ │ │ │ +70EF8 UID Size 04 (4) │ │ │ │ +70EF9 UID 00000000 (0) │ │ │ │ +70EFD GID Size 04 (4) │ │ │ │ +70EFE GID 00000000 (0) │ │ │ │ +70F02 PAYLOAD │ │ │ │ + │ │ │ │ +72794 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +72798 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +72799 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7279A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7279C Compression Method 0008 (8) 'Deflated' │ │ │ │ +7279E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +727A2 CRC 63FED801 (1677645825) │ │ │ │ +727A6 Compressed Size 0000164D (5709) │ │ │ │ +727AA Uncompressed Size 00003A9B (15003) │ │ │ │ +727AE Filename Length 0015 (21) │ │ │ │ +727B0 Extra Length 001C (28) │ │ │ │ +727B2 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x727B2: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +727C7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +727C9 Length 0009 (9) │ │ │ │ +727CB Flags 03 (3) 'Modification Access' │ │ │ │ +727CC Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +727D0 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +727D4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +727D6 Length 000B (11) │ │ │ │ +727D8 Version 01 (1) │ │ │ │ +727D9 UID Size 04 (4) │ │ │ │ +727DA UID 00000000 (0) │ │ │ │ +727DE GID Size 04 (4) │ │ │ │ +727DF GID 00000000 (0) │ │ │ │ +727E3 PAYLOAD │ │ │ │ + │ │ │ │ +73E30 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +73E34 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +73E35 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +73E36 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +73E38 Compression Method 0008 (8) 'Deflated' │ │ │ │ +73E3A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +73E3E CRC 3B61973B (996251451) │ │ │ │ +73E42 Compressed Size 00003B53 (15187) │ │ │ │ +73E46 Uncompressed Size 0001185B (71771) │ │ │ │ +73E4A Filename Length 0016 (22) │ │ │ │ +73E4C Extra Length 001C (28) │ │ │ │ +73E4E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x73E4E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +73E64 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +73E66 Length 0009 (9) │ │ │ │ +73E68 Flags 03 (3) 'Modification Access' │ │ │ │ +73E69 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +73E6D Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +73E71 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +73E73 Length 000B (11) │ │ │ │ +73E75 Version 01 (1) │ │ │ │ +73E76 UID Size 04 (4) │ │ │ │ +73E77 UID 00000000 (0) │ │ │ │ +73E7B GID Size 04 (4) │ │ │ │ +73E7C GID 00000000 (0) │ │ │ │ +73E80 PAYLOAD │ │ │ │ + │ │ │ │ +779D3 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +779D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +779D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +779D9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +779DB Compression Method 0008 (8) 'Deflated' │ │ │ │ +779DD Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +779E1 CRC 69B4C594 (1773454740) │ │ │ │ +779E5 Compressed Size 00003E87 (16007) │ │ │ │ +779E9 Uncompressed Size 0001C17B (115067) │ │ │ │ +779ED Filename Length 0019 (25) │ │ │ │ +779EF Extra Length 001C (28) │ │ │ │ +779F1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x779F1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +77A0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +77A0C Length 0009 (9) │ │ │ │ +77A0E Flags 03 (3) 'Modification Access' │ │ │ │ +77A0F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +77A13 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +77A17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +77A19 Length 000B (11) │ │ │ │ +77A1B Version 01 (1) │ │ │ │ +77A1C UID Size 04 (4) │ │ │ │ +77A1D UID 00000000 (0) │ │ │ │ +77A21 GID Size 04 (4) │ │ │ │ +77A22 GID 00000000 (0) │ │ │ │ +77A26 PAYLOAD │ │ │ │ + │ │ │ │ +7B8AD LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +7B8B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7B8B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7B8B3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7B8B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7B8B7 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +7B8BB CRC 49B1A3B3 (1236378547) │ │ │ │ +7B8BF Compressed Size 00000837 (2103) │ │ │ │ +7B8C3 Uncompressed Size 00003383 (13187) │ │ │ │ +7B8C7 Filename Length 0011 (17) │ │ │ │ +7B8C9 Extra Length 001C (28) │ │ │ │ +7B8CB Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7B8CB: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7B8DC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7B8DE Length 0009 (9) │ │ │ │ +7B8E0 Flags 03 (3) 'Modification Access' │ │ │ │ +7B8E1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +7B8E5 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +7B8E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7B8EB Length 000B (11) │ │ │ │ +7B8ED Version 01 (1) │ │ │ │ +7B8EE UID Size 04 (4) │ │ │ │ +7B8EF UID 00000000 (0) │ │ │ │ +7B8F3 GID Size 04 (4) │ │ │ │ +7B8F4 GID 00000000 (0) │ │ │ │ +7B8F8 PAYLOAD │ │ │ │ + │ │ │ │ +7C12F LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +7C133 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7C134 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7C135 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7C137 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7C139 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +7C13D CRC C9471F9E (3376881566) │ │ │ │ +7C141 Compressed Size 00005184 (20868) │ │ │ │ +7C145 Uncompressed Size 0001FB6C (129900) │ │ │ │ +7C149 Filename Length 0015 (21) │ │ │ │ +7C14B Extra Length 001C (28) │ │ │ │ +7C14D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7C14D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7C162 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7C164 Length 0009 (9) │ │ │ │ +7C166 Flags 03 (3) 'Modification Access' │ │ │ │ +7C167 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +7C16B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +7C16F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7C171 Length 000B (11) │ │ │ │ +7C173 Version 01 (1) │ │ │ │ +7C174 UID Size 04 (4) │ │ │ │ +7C175 UID 00000000 (0) │ │ │ │ +7C179 GID Size 04 (4) │ │ │ │ +7C17A GID 00000000 (0) │ │ │ │ +7C17E PAYLOAD │ │ │ │ + │ │ │ │ +81302 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +81306 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81307 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81308 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8130A Compression Method 0008 (8) 'Deflated' │ │ │ │ +8130C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +81310 CRC C3CD950E (3285030158) │ │ │ │ +81314 Compressed Size 00001B07 (6919) │ │ │ │ +81318 Uncompressed Size 000081CF (33231) │ │ │ │ +8131C Filename Length 0019 (25) │ │ │ │ +8131E Extra Length 001C (28) │ │ │ │ +81320 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x81320: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +81339 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8133B Length 0009 (9) │ │ │ │ +8133D Flags 03 (3) 'Modification Access' │ │ │ │ +8133E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +81342 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +81346 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +81348 Length 000B (11) │ │ │ │ +8134A Version 01 (1) │ │ │ │ +8134B UID Size 04 (4) │ │ │ │ +8134C UID 00000000 (0) │ │ │ │ +81350 GID Size 04 (4) │ │ │ │ +81351 GID 00000000 (0) │ │ │ │ +81355 PAYLOAD │ │ │ │ + │ │ │ │ +82E5C LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +82E60 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +82E61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +82E62 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +82E64 Compression Method 0008 (8) 'Deflated' │ │ │ │ +82E66 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +82E6A CRC 15039DB2 (352558514) │ │ │ │ +82E6E Compressed Size 00000D96 (3478) │ │ │ │ +82E72 Uncompressed Size 00002E9F (11935) │ │ │ │ +82E76 Filename Length 0018 (24) │ │ │ │ +82E78 Extra Length 001C (28) │ │ │ │ +82E7A Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x82E7A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +82E92 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +82E94 Length 0009 (9) │ │ │ │ +82E96 Flags 03 (3) 'Modification Access' │ │ │ │ +82E97 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +82E9B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +82E9F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +82EA1 Length 000B (11) │ │ │ │ +82EA3 Version 01 (1) │ │ │ │ +82EA4 UID Size 04 (4) │ │ │ │ +82EA5 UID 00000000 (0) │ │ │ │ +82EA9 GID Size 04 (4) │ │ │ │ +82EAA GID 00000000 (0) │ │ │ │ +82EAE PAYLOAD │ │ │ │ + │ │ │ │ +83C44 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ +83C48 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +83C49 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +83C4A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +83C4C Compression Method 0008 (8) 'Deflated' │ │ │ │ +83C4E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +83C52 CRC B429472D (3022604077) │ │ │ │ +83C56 Compressed Size 000001E1 (481) │ │ │ │ +83C5A Uncompressed Size 00000323 (803) │ │ │ │ +83C5E Filename Length 0011 (17) │ │ │ │ +83C60 Extra Length 001C (28) │ │ │ │ +83C62 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x83C62: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +83C73 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +83C75 Length 0009 (9) │ │ │ │ +83C77 Flags 03 (3) 'Modification Access' │ │ │ │ +83C78 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +83C7C Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +83C80 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +83C82 Length 000B (11) │ │ │ │ +83C84 Version 01 (1) │ │ │ │ +83C85 UID Size 04 (4) │ │ │ │ +83C86 UID 00000000 (0) │ │ │ │ +83C8A GID Size 04 (4) │ │ │ │ +83C8B GID 00000000 (0) │ │ │ │ +83C8F PAYLOAD │ │ │ │ + │ │ │ │ +83E70 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +83E74 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +83E75 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +83E76 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +83E78 Compression Method 0008 (8) 'Deflated' │ │ │ │ +83E7A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +83E7E CRC 1E45F0E3 (507900131) │ │ │ │ +83E82 Compressed Size 000006C1 (1729) │ │ │ │ +83E86 Uncompressed Size 00001439 (5177) │ │ │ │ +83E8A Filename Length 0019 (25) │ │ │ │ +83E8C Extra Length 001C (28) │ │ │ │ +83E8E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x83E8E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +83EA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +83EA9 Length 0009 (9) │ │ │ │ +83EAB Flags 03 (3) 'Modification Access' │ │ │ │ +83EAC Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +83EB0 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +83EB4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +83EB6 Length 000B (11) │ │ │ │ +83EB8 Version 01 (1) │ │ │ │ +83EB9 UID Size 04 (4) │ │ │ │ +83EBA UID 00000000 (0) │ │ │ │ +83EBE GID Size 04 (4) │ │ │ │ +83EBF GID 00000000 (0) │ │ │ │ +83EC3 PAYLOAD │ │ │ │ + │ │ │ │ +84584 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +84588 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +84589 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8458A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8458C Compression Method 0008 (8) 'Deflated' │ │ │ │ +8458E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +84592 CRC 4E3CD307 (1312609031) │ │ │ │ +84596 Compressed Size 00001B8A (7050) │ │ │ │ +8459A Uncompressed Size 00009F03 (40707) │ │ │ │ +8459E Filename Length 0018 (24) │ │ │ │ +845A0 Extra Length 001C (28) │ │ │ │ +845A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x845A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +845BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +845BC Length 0009 (9) │ │ │ │ +845BE Flags 03 (3) 'Modification Access' │ │ │ │ +845BF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +845C3 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +845C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +845C9 Length 000B (11) │ │ │ │ +845CB Version 01 (1) │ │ │ │ +845CC UID Size 04 (4) │ │ │ │ +845CD UID 00000000 (0) │ │ │ │ +845D1 GID Size 04 (4) │ │ │ │ +845D2 GID 00000000 (0) │ │ │ │ +845D6 PAYLOAD │ │ │ │ + │ │ │ │ +86160 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +86164 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +86165 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +86166 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +86168 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8616A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +8616E CRC 2C0CA940 (739027264) │ │ │ │ +86172 Compressed Size 000016F8 (5880) │ │ │ │ +86176 Uncompressed Size 00008AB6 (35510) │ │ │ │ +8617A Filename Length 0012 (18) │ │ │ │ +8617C Extra Length 001C (28) │ │ │ │ +8617E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8617E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +86190 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +86192 Length 0009 (9) │ │ │ │ +86194 Flags 03 (3) 'Modification Access' │ │ │ │ +86195 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +86199 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8619D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8619F Length 000B (11) │ │ │ │ +861A1 Version 01 (1) │ │ │ │ +861A2 UID Size 04 (4) │ │ │ │ +861A3 UID 00000000 (0) │ │ │ │ +861A7 GID Size 04 (4) │ │ │ │ +861A8 GID 00000000 (0) │ │ │ │ +861AC PAYLOAD │ │ │ │ + │ │ │ │ +878A4 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +878A8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +878A9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +878AA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +878AC Compression Method 0008 (8) 'Deflated' │ │ │ │ +878AE Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +878B2 CRC 38930BD1 (949160913) │ │ │ │ +878B6 Compressed Size 00001E14 (7700) │ │ │ │ +878BA Uncompressed Size 00008803 (34819) │ │ │ │ +878BE Filename Length 0016 (22) │ │ │ │ +878C0 Extra Length 001C (28) │ │ │ │ +878C2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x878C2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +878D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +878DA Length 0009 (9) │ │ │ │ +878DC Flags 03 (3) 'Modification Access' │ │ │ │ +878DD Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +878E1 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +878E5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +878E7 Length 000B (11) │ │ │ │ +878E9 Version 01 (1) │ │ │ │ +878EA UID Size 04 (4) │ │ │ │ +878EB UID 00000000 (0) │ │ │ │ +878EF GID Size 04 (4) │ │ │ │ +878F0 GID 00000000 (0) │ │ │ │ +878F4 PAYLOAD │ │ │ │ + │ │ │ │ +89708 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +8970C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8970D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8970E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +89710 Compression Method 0008 (8) 'Deflated' │ │ │ │ +89712 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +89716 CRC 85636E77 (2237886071) │ │ │ │ +8971A Compressed Size 000029AB (10667) │ │ │ │ +8971E Uncompressed Size 0000D04F (53327) │ │ │ │ +89722 Filename Length 001A (26) │ │ │ │ +89724 Extra Length 001C (28) │ │ │ │ +89726 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x89726: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +89740 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +89742 Length 0009 (9) │ │ │ │ +89744 Flags 03 (3) 'Modification Access' │ │ │ │ +89745 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +89749 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8974D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8974F Length 000B (11) │ │ │ │ +89751 Version 01 (1) │ │ │ │ +89752 UID Size 04 (4) │ │ │ │ +89753 UID 00000000 (0) │ │ │ │ +89757 GID Size 04 (4) │ │ │ │ +89758 GID 00000000 (0) │ │ │ │ +8975C PAYLOAD │ │ │ │ + │ │ │ │ +8C107 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +8C10B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8C10C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8C10D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8C10F Compression Method 0008 (8) 'Deflated' │ │ │ │ +8C111 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +8C115 CRC 548FBB56 (1418705750) │ │ │ │ +8C119 Compressed Size 000009AC (2476) │ │ │ │ +8C11D Uncompressed Size 00001DB6 (7606) │ │ │ │ +8C121 Filename Length 0018 (24) │ │ │ │ +8C123 Extra Length 001C (28) │ │ │ │ +8C125 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8C125: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8C13D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8C13F Length 0009 (9) │ │ │ │ +8C141 Flags 03 (3) 'Modification Access' │ │ │ │ +8C142 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8C146 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8C14A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8C14C Length 000B (11) │ │ │ │ +8C14E Version 01 (1) │ │ │ │ +8C14F UID Size 04 (4) │ │ │ │ +8C150 UID 00000000 (0) │ │ │ │ +8C154 GID Size 04 (4) │ │ │ │ +8C155 GID 00000000 (0) │ │ │ │ +8C159 PAYLOAD │ │ │ │ + │ │ │ │ +8CB05 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +8CB09 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8CB0A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8CB0B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8CB0D Compression Method 0008 (8) 'Deflated' │ │ │ │ +8CB0F Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +8CB13 CRC F5E2129F (4125233823) │ │ │ │ +8CB17 Compressed Size 000016BC (5820) │ │ │ │ +8CB1B Uncompressed Size 000016CD (5837) │ │ │ │ +8CB1F Filename Length 0015 (21) │ │ │ │ +8CB21 Extra Length 001C (28) │ │ │ │ +8CB23 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8CB23: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8CB38 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8CB3A Length 0009 (9) │ │ │ │ +8CB3C Flags 03 (3) 'Modification Access' │ │ │ │ +8CB3D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8CB41 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8CB45 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8CB47 Length 000B (11) │ │ │ │ +8CB49 Version 01 (1) │ │ │ │ +8CB4A UID Size 04 (4) │ │ │ │ +8CB4B UID 00000000 (0) │ │ │ │ +8CB4F GID Size 04 (4) │ │ │ │ +8CB50 GID 00000000 (0) │ │ │ │ +8CB54 PAYLOAD │ │ │ │ + │ │ │ │ +8E210 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +8E214 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8E215 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8E216 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8E218 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8E21A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +8E21E CRC F5E2129F (4125233823) │ │ │ │ +8E222 Compressed Size 000016BC (5820) │ │ │ │ +8E226 Uncompressed Size 000016CD (5837) │ │ │ │ +8E22A Filename Length 001C (28) │ │ │ │ +8E22C Extra Length 001C (28) │ │ │ │ +8E22E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8E22E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8E24A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8E24C Length 0009 (9) │ │ │ │ +8E24E Flags 03 (3) 'Modification Access' │ │ │ │ +8E24F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8E253 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8E257 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8E259 Length 000B (11) │ │ │ │ +8E25B Version 01 (1) │ │ │ │ +8E25C UID Size 04 (4) │ │ │ │ +8E25D UID 00000000 (0) │ │ │ │ +8E261 GID Size 04 (4) │ │ │ │ +8E262 GID 00000000 (0) │ │ │ │ +8E266 PAYLOAD │ │ │ │ + │ │ │ │ +8F922 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +8F926 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +8F927 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8F928 General Purpose Flag 0000 (0) │ │ │ │ +8F92A Compression Method 0000 (0) 'Stored' │ │ │ │ +8F92C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +8F930 CRC FC95F24B (4237685323) │ │ │ │ +8F934 Compressed Size 00001B84 (7044) │ │ │ │ +8F938 Uncompressed Size 00001B84 (7044) │ │ │ │ +8F93C Filename Length 0016 (22) │ │ │ │ +8F93E Extra Length 001C (28) │ │ │ │ +8F940 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8F940: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8F956 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8F958 Length 0009 (9) │ │ │ │ +8F95A Flags 03 (3) 'Modification Access' │ │ │ │ +8F95B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8F95F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +8F963 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8F965 Length 000B (11) │ │ │ │ +8F967 Version 01 (1) │ │ │ │ +8F968 UID Size 04 (4) │ │ │ │ +8F969 UID 00000000 (0) │ │ │ │ +8F96D GID Size 04 (4) │ │ │ │ +8F96E GID 00000000 (0) │ │ │ │ +8F972 PAYLOAD │ │ │ │ + │ │ │ │ +914F6 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +914FA Extract Zip Spec 0A (10) '1.0' │ │ │ │ +914FB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +914FC General Purpose Flag 0000 (0) │ │ │ │ +914FE Compression Method 0000 (0) 'Stored' │ │ │ │ +91500 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +91504 CRC D0D71F86 (3503759238) │ │ │ │ +91508 Compressed Size 00000B7B (2939) │ │ │ │ +9150C Uncompressed Size 00000B7B (2939) │ │ │ │ +91510 Filename Length 0016 (22) │ │ │ │ +91512 Extra Length 001C (28) │ │ │ │ +91514 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x91514: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9152A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9152C Length 0009 (9) │ │ │ │ +9152E Flags 03 (3) 'Modification Access' │ │ │ │ +9152F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +91533 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +91537 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +91539 Length 000B (11) │ │ │ │ +9153B Version 01 (1) │ │ │ │ +9153C UID Size 04 (4) │ │ │ │ +9153D UID 00000000 (0) │ │ │ │ +91541 GID Size 04 (4) │ │ │ │ +91542 GID 00000000 (0) │ │ │ │ +91546 PAYLOAD │ │ │ │ + │ │ │ │ +920C1 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +920C5 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +920C6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +920C7 General Purpose Flag 0000 (0) │ │ │ │ +920C9 Compression Method 0000 (0) 'Stored' │ │ │ │ +920CB Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +920CF CRC FFF9C4D2 (4294558930) │ │ │ │ +920D3 Compressed Size 0000138F (5007) │ │ │ │ +920D7 Uncompressed Size 0000138F (5007) │ │ │ │ +920DB Filename Length 0016 (22) │ │ │ │ +920DD Extra Length 001C (28) │ │ │ │ +920DF Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x920DF: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +920F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +920F7 Length 0009 (9) │ │ │ │ +920F9 Flags 03 (3) 'Modification Access' │ │ │ │ +920FA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +920FE Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +92102 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +92104 Length 000B (11) │ │ │ │ +92106 Version 01 (1) │ │ │ │ +92107 UID Size 04 (4) │ │ │ │ +92108 UID 00000000 (0) │ │ │ │ +9210C GID Size 04 (4) │ │ │ │ +9210D GID 00000000 (0) │ │ │ │ +92111 PAYLOAD │ │ │ │ + │ │ │ │ +934A0 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +934A4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +934A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +934A6 General Purpose Flag 0000 (0) │ │ │ │ +934A8 Compression Method 0000 (0) 'Stored' │ │ │ │ +934AA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +934AE CRC A1037E8E (2701360782) │ │ │ │ +934B2 Compressed Size 0000145E (5214) │ │ │ │ +934B6 Uncompressed Size 0000145E (5214) │ │ │ │ +934BA Filename Length 0016 (22) │ │ │ │ +934BC Extra Length 001C (28) │ │ │ │ +934BE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x934BE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +934D4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +934D6 Length 0009 (9) │ │ │ │ +934D8 Flags 03 (3) 'Modification Access' │ │ │ │ +934D9 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +934DD Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +934E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +934E3 Length 000B (11) │ │ │ │ +934E5 Version 01 (1) │ │ │ │ +934E6 UID Size 04 (4) │ │ │ │ +934E7 UID 00000000 (0) │ │ │ │ +934EB GID Size 04 (4) │ │ │ │ +934EC GID 00000000 (0) │ │ │ │ +934F0 PAYLOAD │ │ │ │ + │ │ │ │ +9494E LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +94952 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +94953 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +94954 General Purpose Flag 0000 (0) │ │ │ │ +94956 Compression Method 0000 (0) 'Stored' │ │ │ │ +94958 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9495C CRC 5E9E64F1 (1587438833) │ │ │ │ +94960 Compressed Size 000008EC (2284) │ │ │ │ +94964 Uncompressed Size 000008EC (2284) │ │ │ │ +94968 Filename Length 0016 (22) │ │ │ │ +9496A Extra Length 001C (28) │ │ │ │ +9496C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9496C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +94982 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +94984 Length 0009 (9) │ │ │ │ +94986 Flags 03 (3) 'Modification Access' │ │ │ │ +94987 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9498B Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9498F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +94991 Length 000B (11) │ │ │ │ +94993 Version 01 (1) │ │ │ │ +94994 UID Size 04 (4) │ │ │ │ +94995 UID 00000000 (0) │ │ │ │ +94999 GID Size 04 (4) │ │ │ │ +9499A GID 00000000 (0) │ │ │ │ +9499E PAYLOAD │ │ │ │ + │ │ │ │ +9528A LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +9528E Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9528F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +95290 General Purpose Flag 0000 (0) │ │ │ │ +95292 Compression Method 0000 (0) 'Stored' │ │ │ │ +95294 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +95298 CRC 42E340AB (1122189483) │ │ │ │ +9529C Compressed Size 00001F2E (7982) │ │ │ │ +952A0 Uncompressed Size 00001F2E (7982) │ │ │ │ +952A4 Filename Length 001E (30) │ │ │ │ +952A6 Extra Length 001C (28) │ │ │ │ +952A8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x952A8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +952C6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +952C8 Length 0009 (9) │ │ │ │ +952CA Flags 03 (3) 'Modification Access' │ │ │ │ +952CB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +952CF Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +952D3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +952D5 Length 000B (11) │ │ │ │ +952D7 Version 01 (1) │ │ │ │ +952D8 UID Size 04 (4) │ │ │ │ +952D9 UID 00000000 (0) │ │ │ │ +952DD GID Size 04 (4) │ │ │ │ +952DE GID 00000000 (0) │ │ │ │ +952E2 PAYLOAD │ │ │ │ + │ │ │ │ +97210 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +97214 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +97215 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +97216 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +97218 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9721A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9721E CRC DC7E4AC1 (3699264193) │ │ │ │ +97222 Compressed Size 00003D72 (15730) │ │ │ │ +97226 Uncompressed Size 00016649 (91721) │ │ │ │ +9722A Filename Length 001A (26) │ │ │ │ +9722C Extra Length 001C (28) │ │ │ │ +9722E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9722E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +97248 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9724A Length 0009 (9) │ │ │ │ +9724C Flags 03 (3) 'Modification Access' │ │ │ │ +9724D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +97251 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +97255 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +97257 Length 000B (11) │ │ │ │ +97259 Version 01 (1) │ │ │ │ +9725A UID Size 04 (4) │ │ │ │ +9725B UID 00000000 (0) │ │ │ │ +9725F GID Size 04 (4) │ │ │ │ +97260 GID 00000000 (0) │ │ │ │ +97264 PAYLOAD │ │ │ │ + │ │ │ │ +9AFD6 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +9AFDA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9AFDB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9AFDC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9AFDE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9AFE0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9AFE4 CRC 79CCCF16 (2043465494) │ │ │ │ +9AFE8 Compressed Size 000029BA (10682) │ │ │ │ +9AFEC Uncompressed Size 0000BA6A (47722) │ │ │ │ +9AFF0 Filename Length 0018 (24) │ │ │ │ +9AFF2 Extra Length 001C (28) │ │ │ │ +9AFF4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9AFF4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9B00C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9B00E Length 0009 (9) │ │ │ │ +9B010 Flags 03 (3) 'Modification Access' │ │ │ │ +9B011 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9B015 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9B019 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9B01B Length 000B (11) │ │ │ │ +9B01D Version 01 (1) │ │ │ │ +9B01E UID Size 04 (4) │ │ │ │ +9B01F UID 00000000 (0) │ │ │ │ +9B023 GID Size 04 (4) │ │ │ │ +9B024 GID 00000000 (0) │ │ │ │ +9B028 PAYLOAD │ │ │ │ + │ │ │ │ +9D9E2 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +9D9E6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9D9E7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9D9E8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9D9EA Compression Method 0008 (8) 'Deflated' │ │ │ │ +9D9EC Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9D9F0 CRC DCB3B516 (3702764822) │ │ │ │ +9D9F4 Compressed Size 000000AE (174) │ │ │ │ +9D9F8 Uncompressed Size 000000FC (252) │ │ │ │ +9D9FC Filename Length 0016 (22) │ │ │ │ +9D9FE Extra Length 001C (28) │ │ │ │ +9DA00 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DA00: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DA16 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DA18 Length 0009 (9) │ │ │ │ +9DA1A Flags 03 (3) 'Modification Access' │ │ │ │ +9DA1B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DA1F Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DA23 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DA25 Length 000B (11) │ │ │ │ +9DA27 Version 01 (1) │ │ │ │ +9DA28 UID Size 04 (4) │ │ │ │ +9DA29 UID 00000000 (0) │ │ │ │ +9DA2D GID Size 04 (4) │ │ │ │ +9DA2E GID 00000000 (0) │ │ │ │ +9DA32 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -9DAD4 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -9DAD8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DAD9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DADA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DADC Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DADE Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DAE2 CRC 58439733 (1480824627) │ │ │ │ -9DAE6 Compressed Size 00000077 (119) │ │ │ │ -9DAEA Uncompressed Size 000000A2 (162) │ │ │ │ -9DAEE Filename Length 002D (45) │ │ │ │ -9DAF0 Extra Length 001C (28) │ │ │ │ -9DAF2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DAF2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DB1F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DB21 Length 0009 (9) │ │ │ │ -9DB23 Flags 03 (3) 'Modification Access' │ │ │ │ -9DB24 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DB28 Access Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DB2C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DB2E Length 000B (11) │ │ │ │ -9DB30 Version 01 (1) │ │ │ │ -9DB31 UID Size 04 (4) │ │ │ │ -9DB32 UID 00000000 (0) │ │ │ │ -9DB36 GID Size 04 (4) │ │ │ │ -9DB37 GID 00000000 (0) │ │ │ │ -9DB3B PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -9DBB2 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -9DBB6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DBB7 Created OS 03 (3) 'Unix' │ │ │ │ -9DBB8 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9DBB9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DBBA General Purpose Flag 0000 (0) │ │ │ │ -9DBBC Compression Method 0000 (0) 'Stored' │ │ │ │ -9DBBE Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DBC2 CRC 2CAB616F (749429103) │ │ │ │ -9DBC6 Compressed Size 00000014 (20) │ │ │ │ -9DBCA Uncompressed Size 00000014 (20) │ │ │ │ -9DBCE Filename Length 0008 (8) │ │ │ │ -9DBD0 Extra Length 0018 (24) │ │ │ │ -9DBD2 Comment Length 0000 (0) │ │ │ │ -9DBD4 Disk Start 0000 (0) │ │ │ │ -9DBD6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DBD8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DBDC Local Header Offset 00000000 (0) │ │ │ │ -9DBE0 Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DBE0: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DBE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DBEA Length 0005 (5) │ │ │ │ -9DBEC Flags 01 (1) 'Modification' │ │ │ │ -9DBED Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DBF1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DBF3 Length 000B (11) │ │ │ │ -9DBF5 Version 01 (1) │ │ │ │ -9DBF6 UID Size 04 (4) │ │ │ │ -9DBF7 UID 00000000 (0) │ │ │ │ -9DBFB GID Size 04 (4) │ │ │ │ -9DBFC GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DC00 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -9DC04 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DC05 Created OS 03 (3) 'Unix' │ │ │ │ -9DC06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DC07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DC08 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DC0A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DC0C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DC10 CRC 203D4469 (540886121) │ │ │ │ -9DC14 Compressed Size 000015AD (5549) │ │ │ │ -9DC18 Uncompressed Size 00004602 (17922) │ │ │ │ -9DC1C Filename Length 0014 (20) │ │ │ │ -9DC1E Extra Length 0018 (24) │ │ │ │ -9DC20 Comment Length 0000 (0) │ │ │ │ -9DC22 Disk Start 0000 (0) │ │ │ │ -9DC24 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DC26 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DC2A Local Header Offset 00000056 (86) │ │ │ │ -9DC2E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DC2E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DC42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DC44 Length 0005 (5) │ │ │ │ -9DC46 Flags 01 (1) 'Modification' │ │ │ │ -9DC47 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DC4B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DC4D Length 000B (11) │ │ │ │ -9DC4F Version 01 (1) │ │ │ │ -9DC50 UID Size 04 (4) │ │ │ │ -9DC51 UID 00000000 (0) │ │ │ │ -9DC55 GID Size 04 (4) │ │ │ │ -9DC56 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DC5A CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -9DC5E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DC5F Created OS 03 (3) 'Unix' │ │ │ │ -9DC60 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DC61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DC62 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DC64 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DC66 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DC6A CRC 1EC1D7C5 (516020165) │ │ │ │ -9DC6E Compressed Size 000006D5 (1749) │ │ │ │ -9DC72 Uncompressed Size 00001241 (4673) │ │ │ │ -9DC76 Filename Length 0013 (19) │ │ │ │ -9DC78 Extra Length 0018 (24) │ │ │ │ -9DC7A Comment Length 0000 (0) │ │ │ │ -9DC7C Disk Start 0000 (0) │ │ │ │ -9DC7E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DC80 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DC84 Local Header Offset 00001651 (5713) │ │ │ │ -9DC88 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DC88: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DC9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DC9D Length 0005 (5) │ │ │ │ -9DC9F Flags 01 (1) 'Modification' │ │ │ │ -9DCA0 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DCA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DCA6 Length 000B (11) │ │ │ │ -9DCA8 Version 01 (1) │ │ │ │ -9DCA9 UID Size 04 (4) │ │ │ │ -9DCAA UID 00000000 (0) │ │ │ │ -9DCAE GID Size 04 (4) │ │ │ │ -9DCAF GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DCB3 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -9DCB7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DCB8 Created OS 03 (3) 'Unix' │ │ │ │ -9DCB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DCBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DCBB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DCBD Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DCBF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DCC3 CRC DD59CFB1 (3713650609) │ │ │ │ -9DCC7 Compressed Size 00002DA9 (11689) │ │ │ │ -9DCCB Uncompressed Size 0000D0BF (53439) │ │ │ │ -9DCCF Filename Length 0014 (20) │ │ │ │ -9DCD1 Extra Length 0018 (24) │ │ │ │ -9DCD3 Comment Length 0000 (0) │ │ │ │ -9DCD5 Disk Start 0000 (0) │ │ │ │ -9DCD7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DCD9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DCDD Local Header Offset 00001D73 (7539) │ │ │ │ -9DCE1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DCE1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DCF5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DCF7 Length 0005 (5) │ │ │ │ -9DCF9 Flags 01 (1) 'Modification' │ │ │ │ -9DCFA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DCFE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DD00 Length 000B (11) │ │ │ │ -9DD02 Version 01 (1) │ │ │ │ -9DD03 UID Size 04 (4) │ │ │ │ -9DD04 UID 00000000 (0) │ │ │ │ -9DD08 GID Size 04 (4) │ │ │ │ -9DD09 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DD0D CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -9DD11 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DD12 Created OS 03 (3) 'Unix' │ │ │ │ -9DD13 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DD14 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DD15 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DD17 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DD19 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DD1D CRC B8F4A452 (3103040594) │ │ │ │ -9DD21 Compressed Size 000003F0 (1008) │ │ │ │ -9DD25 Uncompressed Size 00000876 (2166) │ │ │ │ -9DD29 Filename Length 0014 (20) │ │ │ │ -9DD2B Extra Length 0018 (24) │ │ │ │ -9DD2D Comment Length 0000 (0) │ │ │ │ -9DD2F Disk Start 0000 (0) │ │ │ │ -9DD31 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DD33 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DD37 Local Header Offset 00004B6A (19306) │ │ │ │ -9DD3B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DD3B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DD4F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DD51 Length 0005 (5) │ │ │ │ -9DD53 Flags 01 (1) 'Modification' │ │ │ │ -9DD54 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DD58 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DD5A Length 000B (11) │ │ │ │ -9DD5C Version 01 (1) │ │ │ │ -9DD5D UID Size 04 (4) │ │ │ │ -9DD5E UID 00000000 (0) │ │ │ │ -9DD62 GID Size 04 (4) │ │ │ │ -9DD63 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DD67 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -9DD6B Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DD6C Created OS 03 (3) 'Unix' │ │ │ │ -9DD6D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DD6E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DD6F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DD71 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DD73 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DD77 CRC D32DA388 (3542983560) │ │ │ │ -9DD7B Compressed Size 000001AE (430) │ │ │ │ -9DD7F Uncompressed Size 000002FC (764) │ │ │ │ -9DD83 Filename Length 0011 (17) │ │ │ │ -9DD85 Extra Length 0018 (24) │ │ │ │ -9DD87 Comment Length 0000 (0) │ │ │ │ -9DD89 Disk Start 0000 (0) │ │ │ │ -9DD8B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DD8D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DD91 Local Header Offset 00004FA8 (20392) │ │ │ │ -9DD95 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DD95: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DDA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DDA8 Length 0005 (5) │ │ │ │ -9DDAA Flags 01 (1) 'Modification' │ │ │ │ -9DDAB Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DDAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DDB1 Length 000B (11) │ │ │ │ -9DDB3 Version 01 (1) │ │ │ │ -9DDB4 UID Size 04 (4) │ │ │ │ -9DDB5 UID 00000000 (0) │ │ │ │ -9DDB9 GID Size 04 (4) │ │ │ │ -9DDBA GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DDBE CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -9DDC2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DDC3 Created OS 03 (3) 'Unix' │ │ │ │ -9DDC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DDC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DDC6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DDC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DDCA Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DDCE CRC D5BF807E (3586097278) │ │ │ │ -9DDD2 Compressed Size 000020C6 (8390) │ │ │ │ -9DDD6 Uncompressed Size 0000B4B0 (46256) │ │ │ │ -9DDDA Filename Length 001B (27) │ │ │ │ -9DDDC Extra Length 0018 (24) │ │ │ │ -9DDDE Comment Length 0000 (0) │ │ │ │ -9DDE0 Disk Start 0000 (0) │ │ │ │ -9DDE2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DDE4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DDE8 Local Header Offset 000051A1 (20897) │ │ │ │ -9DDEC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DDEC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DE07 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DE09 Length 0005 (5) │ │ │ │ -9DE0B Flags 01 (1) 'Modification' │ │ │ │ -9DE0C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DE10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DE12 Length 000B (11) │ │ │ │ -9DE14 Version 01 (1) │ │ │ │ -9DE15 UID Size 04 (4) │ │ │ │ -9DE16 UID 00000000 (0) │ │ │ │ -9DE1A GID Size 04 (4) │ │ │ │ -9DE1B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DE1F CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -9DE23 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DE24 Created OS 03 (3) 'Unix' │ │ │ │ -9DE25 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DE26 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DE27 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DE29 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DE2B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DE2F CRC CB35CCED (3409300717) │ │ │ │ -9DE33 Compressed Size 00000E6F (3695) │ │ │ │ -9DE37 Uncompressed Size 000030B2 (12466) │ │ │ │ -9DE3B Filename Length 001D (29) │ │ │ │ -9DE3D Extra Length 0018 (24) │ │ │ │ -9DE3F Comment Length 0000 (0) │ │ │ │ -9DE41 Disk Start 0000 (0) │ │ │ │ -9DE43 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DE45 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DE49 Local Header Offset 000072BC (29372) │ │ │ │ -9DE4D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DE4D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DE6A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DE6C Length 0005 (5) │ │ │ │ -9DE6E Flags 01 (1) 'Modification' │ │ │ │ -9DE6F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DE73 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DE75 Length 000B (11) │ │ │ │ -9DE77 Version 01 (1) │ │ │ │ -9DE78 UID Size 04 (4) │ │ │ │ -9DE79 UID 00000000 (0) │ │ │ │ -9DE7D GID Size 04 (4) │ │ │ │ -9DE7E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DE82 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -9DE86 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DE87 Created OS 03 (3) 'Unix' │ │ │ │ -9DE88 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DE89 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DE8A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DE8C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DE8E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DE92 CRC D01F57B3 (3491714995) │ │ │ │ -9DE96 Compressed Size 00000972 (2418) │ │ │ │ -9DE9A Uncompressed Size 00001CB2 (7346) │ │ │ │ -9DE9E Filename Length 0019 (25) │ │ │ │ -9DEA0 Extra Length 0018 (24) │ │ │ │ -9DEA2 Comment Length 0000 (0) │ │ │ │ -9DEA4 Disk Start 0000 (0) │ │ │ │ -9DEA6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DEA8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DEAC Local Header Offset 00008182 (33154) │ │ │ │ -9DEB0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DEB0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DEC9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DECB Length 0005 (5) │ │ │ │ -9DECD Flags 01 (1) 'Modification' │ │ │ │ -9DECE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DED2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DED4 Length 000B (11) │ │ │ │ -9DED6 Version 01 (1) │ │ │ │ -9DED7 UID Size 04 (4) │ │ │ │ -9DED8 UID 00000000 (0) │ │ │ │ -9DEDC GID Size 04 (4) │ │ │ │ -9DEDD GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DEE1 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -9DEE5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DEE6 Created OS 03 (3) 'Unix' │ │ │ │ -9DEE7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DEE8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DEE9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DEEB Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DEED Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DEF1 CRC 92DCCD7D (2463944061) │ │ │ │ -9DEF5 Compressed Size 0000387F (14463) │ │ │ │ -9DEF9 Uncompressed Size 0000F7F4 (63476) │ │ │ │ -9DEFD Filename Length 0015 (21) │ │ │ │ -9DEFF Extra Length 0018 (24) │ │ │ │ -9DF01 Comment Length 0000 (0) │ │ │ │ -9DF03 Disk Start 0000 (0) │ │ │ │ -9DF05 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DF07 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DF0B Local Header Offset 00008B47 (35655) │ │ │ │ -9DF0F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DF0F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DF24 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DF26 Length 0005 (5) │ │ │ │ -9DF28 Flags 01 (1) 'Modification' │ │ │ │ -9DF29 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DF2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DF2F Length 000B (11) │ │ │ │ -9DF31 Version 01 (1) │ │ │ │ -9DF32 UID Size 04 (4) │ │ │ │ -9DF33 UID 00000000 (0) │ │ │ │ -9DF37 GID Size 04 (4) │ │ │ │ -9DF38 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DF3C CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -9DF40 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DF41 Created OS 03 (3) 'Unix' │ │ │ │ -9DF42 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DF43 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DF44 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DF46 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DF48 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DF4C CRC 2368599F (594041247) │ │ │ │ -9DF50 Compressed Size 0000AAD3 (43731) │ │ │ │ -9DF54 Uncompressed Size 0003DFDE (253918) │ │ │ │ -9DF58 Filename Length 0012 (18) │ │ │ │ -9DF5A Extra Length 0018 (24) │ │ │ │ -9DF5C Comment Length 0000 (0) │ │ │ │ -9DF5E Disk Start 0000 (0) │ │ │ │ -9DF60 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DF62 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DF66 Local Header Offset 0000C415 (50197) │ │ │ │ -9DF6A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DF6A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DF7C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DF7E Length 0005 (5) │ │ │ │ -9DF80 Flags 01 (1) 'Modification' │ │ │ │ -9DF81 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DF85 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DF87 Length 000B (11) │ │ │ │ -9DF89 Version 01 (1) │ │ │ │ -9DF8A UID Size 04 (4) │ │ │ │ -9DF8B UID 00000000 (0) │ │ │ │ -9DF8F GID Size 04 (4) │ │ │ │ -9DF90 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DF94 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -9DF98 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DF99 Created OS 03 (3) 'Unix' │ │ │ │ -9DF9A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DF9B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DF9C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DF9E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DFA0 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DFA4 CRC 2DEC84BF (770475199) │ │ │ │ -9DFA8 Compressed Size 00003B1E (15134) │ │ │ │ -9DFAC Uncompressed Size 0001B2A0 (111264) │ │ │ │ -9DFB0 Filename Length 0015 (21) │ │ │ │ -9DFB2 Extra Length 0018 (24) │ │ │ │ -9DFB4 Comment Length 0000 (0) │ │ │ │ -9DFB6 Disk Start 0000 (0) │ │ │ │ -9DFB8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9DFBA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9DFBE Local Header Offset 00016F34 (94004) │ │ │ │ -9DFC2 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9DFC2: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9DFD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9DFD9 Length 0005 (5) │ │ │ │ -9DFDB Flags 01 (1) 'Modification' │ │ │ │ -9DFDC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9DFE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9DFE2 Length 000B (11) │ │ │ │ -9DFE4 Version 01 (1) │ │ │ │ -9DFE5 UID Size 04 (4) │ │ │ │ -9DFE6 UID 00000000 (0) │ │ │ │ -9DFEA GID Size 04 (4) │ │ │ │ -9DFEB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9DFEF CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -9DFF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9DFF4 Created OS 03 (3) 'Unix' │ │ │ │ -9DFF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9DFF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9DFF7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9DFF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9DFFB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9DFFF CRC 352F19D3 (892279251) │ │ │ │ -9E003 Compressed Size 00009090 (37008) │ │ │ │ -9E007 Uncompressed Size 0003D05F (249951) │ │ │ │ -9E00B Filename Length 0014 (20) │ │ │ │ -9E00D Extra Length 0018 (24) │ │ │ │ -9E00F Comment Length 0000 (0) │ │ │ │ -9E011 Disk Start 0000 (0) │ │ │ │ -9E013 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E015 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E019 Local Header Offset 0001AAA1 (109217) │ │ │ │ -9E01D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E01D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E031 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E033 Length 0005 (5) │ │ │ │ -9E035 Flags 01 (1) 'Modification' │ │ │ │ -9E036 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E03A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E03C Length 000B (11) │ │ │ │ -9E03E Version 01 (1) │ │ │ │ -9E03F UID Size 04 (4) │ │ │ │ -9E040 UID 00000000 (0) │ │ │ │ -9E044 GID Size 04 (4) │ │ │ │ -9E045 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E049 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -9E04D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E04E Created OS 03 (3) 'Unix' │ │ │ │ -9E04F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E050 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E051 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E053 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E055 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E059 CRC A14752A3 (2705805987) │ │ │ │ -9E05D Compressed Size 00002A65 (10853) │ │ │ │ -9E061 Uncompressed Size 0001151F (70943) │ │ │ │ -9E065 Filename Length 0016 (22) │ │ │ │ -9E067 Extra Length 0018 (24) │ │ │ │ -9E069 Comment Length 0000 (0) │ │ │ │ -9E06B Disk Start 0000 (0) │ │ │ │ -9E06D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E06F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E073 Local Header Offset 00023B7F (146303) │ │ │ │ -9E077 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E077: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E08D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E08F Length 0005 (5) │ │ │ │ -9E091 Flags 01 (1) 'Modification' │ │ │ │ -9E092 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E096 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E098 Length 000B (11) │ │ │ │ -9E09A Version 01 (1) │ │ │ │ -9E09B UID Size 04 (4) │ │ │ │ -9E09C UID 00000000 (0) │ │ │ │ -9E0A0 GID Size 04 (4) │ │ │ │ -9E0A1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E0A5 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -9E0A9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E0AA Created OS 03 (3) 'Unix' │ │ │ │ -9E0AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E0AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E0AD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E0AF Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E0B1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E0B5 CRC F1E18143 (4058087747) │ │ │ │ -9E0B9 Compressed Size 000014D8 (5336) │ │ │ │ -9E0BD Uncompressed Size 00005176 (20854) │ │ │ │ -9E0C1 Filename Length 001D (29) │ │ │ │ -9E0C3 Extra Length 0018 (24) │ │ │ │ -9E0C5 Comment Length 0000 (0) │ │ │ │ -9E0C7 Disk Start 0000 (0) │ │ │ │ -9E0C9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E0CB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E0CF Local Header Offset 00026634 (157236) │ │ │ │ -9E0D3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E0D3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E0F0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E0F2 Length 0005 (5) │ │ │ │ -9E0F4 Flags 01 (1) 'Modification' │ │ │ │ -9E0F5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E0F9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E0FB Length 000B (11) │ │ │ │ -9E0FD Version 01 (1) │ │ │ │ -9E0FE UID Size 04 (4) │ │ │ │ -9E0FF UID 00000000 (0) │ │ │ │ -9E103 GID Size 04 (4) │ │ │ │ -9E104 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E108 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -9E10C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E10D Created OS 03 (3) 'Unix' │ │ │ │ -9E10E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E10F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E110 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E112 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E114 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E118 CRC 78898277 (2022277751) │ │ │ │ -9E11C Compressed Size 000037F6 (14326) │ │ │ │ -9E120 Uncompressed Size 0000E9F0 (59888) │ │ │ │ -9E124 Filename Length 001C (28) │ │ │ │ -9E126 Extra Length 0018 (24) │ │ │ │ -9E128 Comment Length 0000 (0) │ │ │ │ -9E12A Disk Start 0000 (0) │ │ │ │ -9E12C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E12E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E132 Local Header Offset 00027B63 (162659) │ │ │ │ -9E136 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E136: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E152 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E154 Length 0005 (5) │ │ │ │ -9E156 Flags 01 (1) 'Modification' │ │ │ │ -9E157 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E15B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E15D Length 000B (11) │ │ │ │ -9E15F Version 01 (1) │ │ │ │ -9E160 UID Size 04 (4) │ │ │ │ -9E161 UID 00000000 (0) │ │ │ │ -9E165 GID Size 04 (4) │ │ │ │ -9E166 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E16A CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -9E16E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E16F Created OS 03 (3) 'Unix' │ │ │ │ -9E170 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E171 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E172 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E174 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E176 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E17A CRC 6A63C5E7 (1784923623) │ │ │ │ -9E17E Compressed Size 000006A0 (1696) │ │ │ │ -9E182 Uncompressed Size 000011F4 (4596) │ │ │ │ -9E186 Filename Length 001C (28) │ │ │ │ -9E188 Extra Length 0018 (24) │ │ │ │ -9E18A Comment Length 0000 (0) │ │ │ │ -9E18C Disk Start 0000 (0) │ │ │ │ -9E18E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E190 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E194 Local Header Offset 0002B3AF (177071) │ │ │ │ -9E198 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E198: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E1B4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E1B6 Length 0005 (5) │ │ │ │ -9E1B8 Flags 01 (1) 'Modification' │ │ │ │ -9E1B9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E1BD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E1BF Length 000B (11) │ │ │ │ -9E1C1 Version 01 (1) │ │ │ │ -9E1C2 UID Size 04 (4) │ │ │ │ -9E1C3 UID 00000000 (0) │ │ │ │ -9E1C7 GID Size 04 (4) │ │ │ │ -9E1C8 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E1CC CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -9E1D0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E1D1 Created OS 03 (3) 'Unix' │ │ │ │ -9E1D2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E1D3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E1D4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E1D6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E1D8 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E1DC CRC 4B420ADA (1262619354) │ │ │ │ -9E1E0 Compressed Size 0000107B (4219) │ │ │ │ -9E1E4 Uncompressed Size 00004BFF (19455) │ │ │ │ -9E1E8 Filename Length 001B (27) │ │ │ │ -9E1EA Extra Length 0018 (24) │ │ │ │ -9E1EC Comment Length 0000 (0) │ │ │ │ -9E1EE Disk Start 0000 (0) │ │ │ │ -9E1F0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E1F2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E1F6 Local Header Offset 0002BAA5 (178853) │ │ │ │ -9E1FA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E1FA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E215 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E217 Length 0005 (5) │ │ │ │ -9E219 Flags 01 (1) 'Modification' │ │ │ │ -9E21A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E21E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E220 Length 000B (11) │ │ │ │ -9E222 Version 01 (1) │ │ │ │ -9E223 UID Size 04 (4) │ │ │ │ -9E224 UID 00000000 (0) │ │ │ │ -9E228 GID Size 04 (4) │ │ │ │ -9E229 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E22D CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -9E231 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E232 Created OS 03 (3) 'Unix' │ │ │ │ -9E233 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E234 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E235 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E237 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E239 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E23D CRC A579921F (2776207903) │ │ │ │ -9E241 Compressed Size 000033AB (13227) │ │ │ │ -9E245 Uncompressed Size 0000BC94 (48276) │ │ │ │ -9E249 Filename Length 001D (29) │ │ │ │ -9E24B Extra Length 0018 (24) │ │ │ │ -9E24D Comment Length 0000 (0) │ │ │ │ -9E24F Disk Start 0000 (0) │ │ │ │ -9E251 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E253 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E257 Local Header Offset 0002CB75 (183157) │ │ │ │ -9E25B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E25B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E278 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E27A Length 0005 (5) │ │ │ │ -9E27C Flags 01 (1) 'Modification' │ │ │ │ -9E27D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E281 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E283 Length 000B (11) │ │ │ │ -9E285 Version 01 (1) │ │ │ │ -9E286 UID Size 04 (4) │ │ │ │ -9E287 UID 00000000 (0) │ │ │ │ -9E28B GID Size 04 (4) │ │ │ │ -9E28C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E290 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -9E294 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E295 Created OS 03 (3) 'Unix' │ │ │ │ -9E296 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E297 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E298 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E29A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E29C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E2A0 CRC 3BBA297E (1002056062) │ │ │ │ -9E2A4 Compressed Size 00000D6C (3436) │ │ │ │ -9E2A8 Uncompressed Size 00003876 (14454) │ │ │ │ -9E2AC Filename Length 001D (29) │ │ │ │ -9E2AE Extra Length 0018 (24) │ │ │ │ -9E2B0 Comment Length 0000 (0) │ │ │ │ -9E2B2 Disk Start 0000 (0) │ │ │ │ -9E2B4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E2B6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E2BA Local Header Offset 0002FF77 (196471) │ │ │ │ -9E2BE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E2BE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E2DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E2DD Length 0005 (5) │ │ │ │ -9E2DF Flags 01 (1) 'Modification' │ │ │ │ -9E2E0 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E2E4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E2E6 Length 000B (11) │ │ │ │ -9E2E8 Version 01 (1) │ │ │ │ -9E2E9 UID Size 04 (4) │ │ │ │ -9E2EA UID 00000000 (0) │ │ │ │ -9E2EE GID Size 04 (4) │ │ │ │ -9E2EF GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E2F3 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -9E2F7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E2F8 Created OS 03 (3) 'Unix' │ │ │ │ -9E2F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E2FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E2FB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E2FD Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E2FF Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E303 CRC CE4502E0 (3460629216) │ │ │ │ -9E307 Compressed Size 00001C6A (7274) │ │ │ │ -9E30B Uncompressed Size 0000C186 (49542) │ │ │ │ -9E30F Filename Length 001A (26) │ │ │ │ -9E311 Extra Length 0018 (24) │ │ │ │ -9E313 Comment Length 0000 (0) │ │ │ │ -9E315 Disk Start 0000 (0) │ │ │ │ -9E317 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E319 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E31D Local Header Offset 00030D3A (199994) │ │ │ │ -9E321 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E321: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E33B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E33D Length 0005 (5) │ │ │ │ -9E33F Flags 01 (1) 'Modification' │ │ │ │ -9E340 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E344 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E346 Length 000B (11) │ │ │ │ -9E348 Version 01 (1) │ │ │ │ -9E349 UID Size 04 (4) │ │ │ │ -9E34A UID 00000000 (0) │ │ │ │ -9E34E GID Size 04 (4) │ │ │ │ -9E34F GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E353 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -9E357 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E358 Created OS 03 (3) 'Unix' │ │ │ │ -9E359 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E35A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E35B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E35D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E35F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E363 CRC 65574916 (1700219158) │ │ │ │ -9E367 Compressed Size 000003A3 (931) │ │ │ │ -9E36B Uncompressed Size 0000088E (2190) │ │ │ │ -9E36F Filename Length 0012 (18) │ │ │ │ -9E371 Extra Length 0018 (24) │ │ │ │ -9E373 Comment Length 0000 (0) │ │ │ │ -9E375 Disk Start 0000 (0) │ │ │ │ -9E377 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E379 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E37D Local Header Offset 000329F8 (207352) │ │ │ │ -9E381 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E381: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E393 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E395 Length 0005 (5) │ │ │ │ -9E397 Flags 01 (1) 'Modification' │ │ │ │ -9E398 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E39C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E39E Length 000B (11) │ │ │ │ -9E3A0 Version 01 (1) │ │ │ │ -9E3A1 UID Size 04 (4) │ │ │ │ -9E3A2 UID 00000000 (0) │ │ │ │ -9E3A6 GID Size 04 (4) │ │ │ │ -9E3A7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E3AB CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -9E3AF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E3B0 Created OS 03 (3) 'Unix' │ │ │ │ -9E3B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E3B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E3B3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E3B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E3B7 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E3BB CRC 83472134 (2202476852) │ │ │ │ -9E3BF Compressed Size 000001D4 (468) │ │ │ │ -9E3C3 Uncompressed Size 00000311 (785) │ │ │ │ -9E3C7 Filename Length 0020 (32) │ │ │ │ -9E3C9 Extra Length 0018 (24) │ │ │ │ -9E3CB Comment Length 0000 (0) │ │ │ │ -9E3CD Disk Start 0000 (0) │ │ │ │ -9E3CF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E3D1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E3D5 Local Header Offset 00032DE7 (208359) │ │ │ │ -9E3D9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E3D9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E3F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E3FB Length 0005 (5) │ │ │ │ -9E3FD Flags 01 (1) 'Modification' │ │ │ │ -9E3FE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E402 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E404 Length 000B (11) │ │ │ │ -9E406 Version 01 (1) │ │ │ │ -9E407 UID Size 04 (4) │ │ │ │ -9E408 UID 00000000 (0) │ │ │ │ -9E40C GID Size 04 (4) │ │ │ │ -9E40D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E411 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -9E415 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E416 Created OS 03 (3) 'Unix' │ │ │ │ -9E417 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E418 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E419 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E41B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E41D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E421 CRC E5289043 (3844640835) │ │ │ │ -9E425 Compressed Size 000017A7 (6055) │ │ │ │ -9E429 Uncompressed Size 00009CD3 (40147) │ │ │ │ -9E42D Filename Length 001B (27) │ │ │ │ -9E42F Extra Length 0018 (24) │ │ │ │ -9E431 Comment Length 0000 (0) │ │ │ │ -9E433 Disk Start 0000 (0) │ │ │ │ -9E435 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E437 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E43B Local Header Offset 00033015 (208917) │ │ │ │ -9E43F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E43F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E45A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E45C Length 0005 (5) │ │ │ │ -9E45E Flags 01 (1) 'Modification' │ │ │ │ -9E45F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E463 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E465 Length 000B (11) │ │ │ │ -9E467 Version 01 (1) │ │ │ │ -9E468 UID Size 04 (4) │ │ │ │ -9E469 UID 00000000 (0) │ │ │ │ -9E46D GID Size 04 (4) │ │ │ │ -9E46E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E472 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -9E476 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E477 Created OS 03 (3) 'Unix' │ │ │ │ -9E478 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E479 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E47A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E47C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E47E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E482 CRC 5F73B5AE (1601418670) │ │ │ │ -9E486 Compressed Size 00001371 (4977) │ │ │ │ -9E48A Uncompressed Size 00003B66 (15206) │ │ │ │ -9E48E Filename Length 0015 (21) │ │ │ │ -9E490 Extra Length 0018 (24) │ │ │ │ -9E492 Comment Length 0000 (0) │ │ │ │ -9E494 Disk Start 0000 (0) │ │ │ │ -9E496 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E498 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E49C Local Header Offset 00034811 (215057) │ │ │ │ -9E4A0 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E4A0: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E4B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E4B7 Length 0005 (5) │ │ │ │ -9E4B9 Flags 01 (1) 'Modification' │ │ │ │ -9E4BA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E4BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E4C0 Length 000B (11) │ │ │ │ -9E4C2 Version 01 (1) │ │ │ │ -9E4C3 UID Size 04 (4) │ │ │ │ -9E4C4 UID 00000000 (0) │ │ │ │ -9E4C8 GID Size 04 (4) │ │ │ │ -9E4C9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E4CD CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -9E4D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E4D2 Created OS 03 (3) 'Unix' │ │ │ │ -9E4D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E4D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E4D5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E4D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E4D9 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E4DD CRC 7FAE09B6 (2142112182) │ │ │ │ -9E4E1 Compressed Size 00000AD1 (2769) │ │ │ │ -9E4E5 Uncompressed Size 00002135 (8501) │ │ │ │ -9E4E9 Filename Length 0011 (17) │ │ │ │ -9E4EB Extra Length 0018 (24) │ │ │ │ -9E4ED Comment Length 0000 (0) │ │ │ │ -9E4EF Disk Start 0000 (0) │ │ │ │ -9E4F1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E4F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E4F7 Local Header Offset 00035BD1 (220113) │ │ │ │ -9E4FB Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E4FB: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E50C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E50E Length 0005 (5) │ │ │ │ -9E510 Flags 01 (1) 'Modification' │ │ │ │ -9E511 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E515 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E517 Length 000B (11) │ │ │ │ -9E519 Version 01 (1) │ │ │ │ -9E51A UID Size 04 (4) │ │ │ │ -9E51B UID 00000000 (0) │ │ │ │ -9E51F GID Size 04 (4) │ │ │ │ -9E520 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E524 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -9E528 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E529 Created OS 03 (3) 'Unix' │ │ │ │ -9E52A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E52B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E52C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E52E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E530 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E534 CRC F2F690A2 (4076245154) │ │ │ │ -9E538 Compressed Size 000003FE (1022) │ │ │ │ -9E53C Uncompressed Size 00000E99 (3737) │ │ │ │ -9E540 Filename Length 0014 (20) │ │ │ │ -9E542 Extra Length 0018 (24) │ │ │ │ -9E544 Comment Length 0000 (0) │ │ │ │ -9E546 Disk Start 0000 (0) │ │ │ │ -9E548 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E54A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E54E Local Header Offset 000366ED (222957) │ │ │ │ -9E552 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E552: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E566 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E568 Length 0005 (5) │ │ │ │ -9E56A Flags 01 (1) 'Modification' │ │ │ │ -9E56B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E56F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E571 Length 000B (11) │ │ │ │ -9E573 Version 01 (1) │ │ │ │ -9E574 UID Size 04 (4) │ │ │ │ -9E575 UID 00000000 (0) │ │ │ │ -9E579 GID Size 04 (4) │ │ │ │ -9E57A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E57E CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -9E582 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E583 Created OS 03 (3) 'Unix' │ │ │ │ -9E584 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E585 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E586 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E588 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E58A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E58E CRC EB83ACB7 (3951275191) │ │ │ │ -9E592 Compressed Size 00001261 (4705) │ │ │ │ -9E596 Uncompressed Size 00003469 (13417) │ │ │ │ -9E59A Filename Length 0014 (20) │ │ │ │ -9E59C Extra Length 0018 (24) │ │ │ │ -9E59E Comment Length 0000 (0) │ │ │ │ -9E5A0 Disk Start 0000 (0) │ │ │ │ -9E5A2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E5A4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E5A8 Local Header Offset 00036B39 (224057) │ │ │ │ -9E5AC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E5AC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E5C0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E5C2 Length 0005 (5) │ │ │ │ -9E5C4 Flags 01 (1) 'Modification' │ │ │ │ -9E5C5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E5C9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E5CB Length 000B (11) │ │ │ │ -9E5CD Version 01 (1) │ │ │ │ -9E5CE UID Size 04 (4) │ │ │ │ -9E5CF UID 00000000 (0) │ │ │ │ -9E5D3 GID Size 04 (4) │ │ │ │ -9E5D4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E5D8 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -9E5DC Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E5DD Created OS 03 (3) 'Unix' │ │ │ │ -9E5DE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E5DF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E5E0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E5E2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E5E4 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E5E8 CRC ED6E01D4 (3983409620) │ │ │ │ -9E5EC Compressed Size 00000AD0 (2768) │ │ │ │ -9E5F0 Uncompressed Size 000022FF (8959) │ │ │ │ -9E5F4 Filename Length 001B (27) │ │ │ │ -9E5F6 Extra Length 0018 (24) │ │ │ │ -9E5F8 Comment Length 0000 (0) │ │ │ │ -9E5FA Disk Start 0000 (0) │ │ │ │ -9E5FC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E5FE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E602 Local Header Offset 00037DE8 (228840) │ │ │ │ -9E606 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E606: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E621 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E623 Length 0005 (5) │ │ │ │ -9E625 Flags 01 (1) 'Modification' │ │ │ │ -9E626 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E62A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E62C Length 000B (11) │ │ │ │ -9E62E Version 01 (1) │ │ │ │ -9E62F UID Size 04 (4) │ │ │ │ -9E630 UID 00000000 (0) │ │ │ │ -9E634 GID Size 04 (4) │ │ │ │ -9E635 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E639 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -9E63D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E63E Created OS 03 (3) 'Unix' │ │ │ │ -9E63F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E640 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E641 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E643 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E645 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E649 CRC 7FE9699A (2146003354) │ │ │ │ -9E64D Compressed Size 00000A8D (2701) │ │ │ │ -9E651 Uncompressed Size 0000237A (9082) │ │ │ │ -9E655 Filename Length 0013 (19) │ │ │ │ -9E657 Extra Length 0018 (24) │ │ │ │ -9E659 Comment Length 0000 (0) │ │ │ │ -9E65B Disk Start 0000 (0) │ │ │ │ -9E65D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E65F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E663 Local Header Offset 0003890D (231693) │ │ │ │ -9E667 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E667: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E67A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E67C Length 0005 (5) │ │ │ │ -9E67E Flags 01 (1) 'Modification' │ │ │ │ -9E67F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E683 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E685 Length 000B (11) │ │ │ │ -9E687 Version 01 (1) │ │ │ │ -9E688 UID Size 04 (4) │ │ │ │ -9E689 UID 00000000 (0) │ │ │ │ -9E68D GID Size 04 (4) │ │ │ │ -9E68E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E692 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -9E696 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E697 Created OS 03 (3) 'Unix' │ │ │ │ -9E698 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E699 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E69A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E69C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E69E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E6A2 CRC DD9D1818 (3718060056) │ │ │ │ -9E6A6 Compressed Size 00000F48 (3912) │ │ │ │ -9E6AA Uncompressed Size 000036F1 (14065) │ │ │ │ -9E6AE Filename Length 000F (15) │ │ │ │ -9E6B0 Extra Length 0018 (24) │ │ │ │ -9E6B2 Comment Length 0000 (0) │ │ │ │ -9E6B4 Disk Start 0000 (0) │ │ │ │ -9E6B6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E6B8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E6BC Local Header Offset 000393E7 (234471) │ │ │ │ -9E6C0 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E6C0: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E6CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E6D1 Length 0005 (5) │ │ │ │ -9E6D3 Flags 01 (1) 'Modification' │ │ │ │ -9E6D4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E6D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E6DA Length 000B (11) │ │ │ │ -9E6DC Version 01 (1) │ │ │ │ -9E6DD UID Size 04 (4) │ │ │ │ -9E6DE UID 00000000 (0) │ │ │ │ -9E6E2 GID Size 04 (4) │ │ │ │ -9E6E3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E6E7 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -9E6EB Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E6EC Created OS 03 (3) 'Unix' │ │ │ │ -9E6ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E6EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E6EF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E6F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E6F3 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E6F7 CRC 54672E6E (1416048238) │ │ │ │ -9E6FB Compressed Size 0000066A (1642) │ │ │ │ -9E6FF Uncompressed Size 000018DF (6367) │ │ │ │ -9E703 Filename Length 000F (15) │ │ │ │ -9E705 Extra Length 0018 (24) │ │ │ │ -9E707 Comment Length 0000 (0) │ │ │ │ -9E709 Disk Start 0000 (0) │ │ │ │ -9E70B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E70D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E711 Local Header Offset 0003A378 (238456) │ │ │ │ -9E715 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E715: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E724 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E726 Length 0005 (5) │ │ │ │ -9E728 Flags 01 (1) 'Modification' │ │ │ │ -9E729 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E72D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E72F Length 000B (11) │ │ │ │ -9E731 Version 01 (1) │ │ │ │ -9E732 UID Size 04 (4) │ │ │ │ -9E733 UID 00000000 (0) │ │ │ │ -9E737 GID Size 04 (4) │ │ │ │ -9E738 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E73C CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -9E740 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E741 Created OS 03 (3) 'Unix' │ │ │ │ -9E742 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E743 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E744 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E746 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E748 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E74C CRC 8AF71E36 (2331450934) │ │ │ │ -9E750 Compressed Size 00001A49 (6729) │ │ │ │ -9E754 Uncompressed Size 000064F2 (25842) │ │ │ │ -9E758 Filename Length 0013 (19) │ │ │ │ -9E75A Extra Length 0018 (24) │ │ │ │ -9E75C Comment Length 0000 (0) │ │ │ │ -9E75E Disk Start 0000 (0) │ │ │ │ -9E760 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E762 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E766 Local Header Offset 0003AA2B (240171) │ │ │ │ -9E76A Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E76A: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E77D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E77F Length 0005 (5) │ │ │ │ -9E781 Flags 01 (1) 'Modification' │ │ │ │ -9E782 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E786 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E788 Length 000B (11) │ │ │ │ -9E78A Version 01 (1) │ │ │ │ -9E78B UID Size 04 (4) │ │ │ │ -9E78C UID 00000000 (0) │ │ │ │ -9E790 GID Size 04 (4) │ │ │ │ -9E791 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E795 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -9E799 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E79A Created OS 03 (3) 'Unix' │ │ │ │ -9E79B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E79C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E79D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E79F Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E7A1 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E7A5 CRC D1448544 (3510928708) │ │ │ │ -9E7A9 Compressed Size 000009A5 (2469) │ │ │ │ -9E7AD Uncompressed Size 00001B64 (7012) │ │ │ │ -9E7B1 Filename Length 0010 (16) │ │ │ │ -9E7B3 Extra Length 0018 (24) │ │ │ │ -9E7B5 Comment Length 0000 (0) │ │ │ │ -9E7B7 Disk Start 0000 (0) │ │ │ │ -9E7B9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E7BB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E7BF Local Header Offset 0003C4C1 (246977) │ │ │ │ -9E7C3 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E7C3: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E7D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E7D5 Length 0005 (5) │ │ │ │ -9E7D7 Flags 01 (1) 'Modification' │ │ │ │ -9E7D8 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E7DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E7DE Length 000B (11) │ │ │ │ -9E7E0 Version 01 (1) │ │ │ │ -9E7E1 UID Size 04 (4) │ │ │ │ -9E7E2 UID 00000000 (0) │ │ │ │ -9E7E6 GID Size 04 (4) │ │ │ │ -9E7E7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E7EB CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -9E7EF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E7F0 Created OS 03 (3) 'Unix' │ │ │ │ -9E7F1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E7F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E7F3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E7F5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E7F7 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E7FB CRC 3E5AEB62 (1046145890) │ │ │ │ -9E7FF Compressed Size 000006B7 (1719) │ │ │ │ -9E803 Uncompressed Size 00001565 (5477) │ │ │ │ -9E807 Filename Length 0012 (18) │ │ │ │ -9E809 Extra Length 0018 (24) │ │ │ │ -9E80B Comment Length 0000 (0) │ │ │ │ -9E80D Disk Start 0000 (0) │ │ │ │ -9E80F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E811 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E815 Local Header Offset 0003CEB0 (249520) │ │ │ │ -9E819 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E819: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E82B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E82D Length 0005 (5) │ │ │ │ -9E82F Flags 01 (1) 'Modification' │ │ │ │ -9E830 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E834 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E836 Length 000B (11) │ │ │ │ -9E838 Version 01 (1) │ │ │ │ -9E839 UID Size 04 (4) │ │ │ │ -9E83A UID 00000000 (0) │ │ │ │ -9E83E GID Size 04 (4) │ │ │ │ -9E83F GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E843 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -9E847 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E848 Created OS 03 (3) 'Unix' │ │ │ │ -9E849 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E84A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E84B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E84D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E84F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E853 CRC C50CE88C (3305957516) │ │ │ │ -9E857 Compressed Size 00002A1A (10778) │ │ │ │ -9E85B Uncompressed Size 0000B1C5 (45509) │ │ │ │ -9E85F Filename Length 0010 (16) │ │ │ │ -9E861 Extra Length 0018 (24) │ │ │ │ -9E863 Comment Length 0000 (0) │ │ │ │ -9E865 Disk Start 0000 (0) │ │ │ │ -9E867 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E869 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E86D Local Header Offset 0003D5B3 (251315) │ │ │ │ -9E871 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E871: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E881 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E883 Length 0005 (5) │ │ │ │ -9E885 Flags 01 (1) 'Modification' │ │ │ │ -9E886 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E88A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E88C Length 000B (11) │ │ │ │ -9E88E Version 01 (1) │ │ │ │ -9E88F UID Size 04 (4) │ │ │ │ -9E890 UID 00000000 (0) │ │ │ │ -9E894 GID Size 04 (4) │ │ │ │ -9E895 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E899 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -9E89D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E89E Created OS 03 (3) 'Unix' │ │ │ │ -9E89F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E8A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E8A1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E8A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E8A5 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E8A9 CRC C59A1802 (3315210242) │ │ │ │ -9E8AD Compressed Size 00001E84 (7812) │ │ │ │ -9E8B1 Uncompressed Size 00009AAA (39594) │ │ │ │ -9E8B5 Filename Length 0012 (18) │ │ │ │ -9E8B7 Extra Length 0018 (24) │ │ │ │ -9E8B9 Comment Length 0000 (0) │ │ │ │ -9E8BB Disk Start 0000 (0) │ │ │ │ -9E8BD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E8BF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E8C3 Local Header Offset 00040017 (262167) │ │ │ │ -9E8C7 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E8C7: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E8D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E8DB Length 0005 (5) │ │ │ │ -9E8DD Flags 01 (1) 'Modification' │ │ │ │ -9E8DE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E8E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E8E4 Length 000B (11) │ │ │ │ -9E8E6 Version 01 (1) │ │ │ │ -9E8E7 UID Size 04 (4) │ │ │ │ -9E8E8 UID 00000000 (0) │ │ │ │ -9E8EC GID Size 04 (4) │ │ │ │ -9E8ED GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E8F1 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -9E8F5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E8F6 Created OS 03 (3) 'Unix' │ │ │ │ -9E8F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E8F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E8F9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E8FB Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E8FD Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E901 CRC A34909C0 (2739472832) │ │ │ │ -9E905 Compressed Size 00001477 (5239) │ │ │ │ -9E909 Uncompressed Size 00007ACF (31439) │ │ │ │ -9E90D Filename Length 0018 (24) │ │ │ │ -9E90F Extra Length 0018 (24) │ │ │ │ -9E911 Comment Length 0000 (0) │ │ │ │ -9E913 Disk Start 0000 (0) │ │ │ │ -9E915 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E917 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E91B Local Header Offset 00041EE7 (270055) │ │ │ │ -9E91F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E91F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E937 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E939 Length 0005 (5) │ │ │ │ -9E93B Flags 01 (1) 'Modification' │ │ │ │ -9E93C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E940 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E942 Length 000B (11) │ │ │ │ -9E944 Version 01 (1) │ │ │ │ -9E945 UID Size 04 (4) │ │ │ │ -9E946 UID 00000000 (0) │ │ │ │ -9E94A GID Size 04 (4) │ │ │ │ -9E94B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E94F CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -9E953 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E954 Created OS 03 (3) 'Unix' │ │ │ │ -9E955 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E956 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E957 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E959 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E95B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E95F CRC C605A326 (3322258214) │ │ │ │ -9E963 Compressed Size 000018CF (6351) │ │ │ │ -9E967 Uncompressed Size 0000A7F4 (42996) │ │ │ │ -9E96B Filename Length 001F (31) │ │ │ │ -9E96D Extra Length 0018 (24) │ │ │ │ -9E96F Comment Length 0000 (0) │ │ │ │ -9E971 Disk Start 0000 (0) │ │ │ │ -9E973 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E975 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E979 Local Header Offset 000433B0 (275376) │ │ │ │ -9E97D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E97D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9E99C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9E99E Length 0005 (5) │ │ │ │ -9E9A0 Flags 01 (1) 'Modification' │ │ │ │ -9E9A1 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9E9A5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9E9A7 Length 000B (11) │ │ │ │ -9E9A9 Version 01 (1) │ │ │ │ -9E9AA UID Size 04 (4) │ │ │ │ -9E9AB UID 00000000 (0) │ │ │ │ -9E9AF GID Size 04 (4) │ │ │ │ -9E9B0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9E9B4 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -9E9B8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9E9B9 Created OS 03 (3) 'Unix' │ │ │ │ -9E9BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9E9BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9E9BC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9E9BE Compression Method 0008 (8) 'Deflated' │ │ │ │ -9E9C0 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9E9C4 CRC FFAF198F (4289665423) │ │ │ │ -9E9C8 Compressed Size 000003F6 (1014) │ │ │ │ -9E9CC Uncompressed Size 000008A3 (2211) │ │ │ │ -9E9D0 Filename Length 001E (30) │ │ │ │ -9E9D2 Extra Length 0018 (24) │ │ │ │ -9E9D4 Comment Length 0000 (0) │ │ │ │ -9E9D6 Disk Start 0000 (0) │ │ │ │ -9E9D8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9E9DA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9E9DE Local Header Offset 00044CD8 (281816) │ │ │ │ -9E9E2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9E9E2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EA00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EA02 Length 0005 (5) │ │ │ │ -9EA04 Flags 01 (1) 'Modification' │ │ │ │ -9EA05 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EA09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EA0B Length 000B (11) │ │ │ │ -9EA0D Version 01 (1) │ │ │ │ -9EA0E UID Size 04 (4) │ │ │ │ -9EA0F UID 00000000 (0) │ │ │ │ -9EA13 GID Size 04 (4) │ │ │ │ -9EA14 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EA18 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -9EA1C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EA1D Created OS 03 (3) 'Unix' │ │ │ │ -9EA1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EA1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EA20 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EA22 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EA24 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EA28 CRC 32E3FA13 (853801491) │ │ │ │ -9EA2C Compressed Size 00004292 (17042) │ │ │ │ -9EA30 Uncompressed Size 0000D8DC (55516) │ │ │ │ -9EA34 Filename Length 0013 (19) │ │ │ │ -9EA36 Extra Length 0018 (24) │ │ │ │ -9EA38 Comment Length 0000 (0) │ │ │ │ -9EA3A Disk Start 0000 (0) │ │ │ │ -9EA3C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EA3E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EA42 Local Header Offset 00045126 (282918) │ │ │ │ -9EA46 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EA46: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EA59 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EA5B Length 0005 (5) │ │ │ │ -9EA5D Flags 01 (1) 'Modification' │ │ │ │ -9EA5E Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EA62 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EA64 Length 000B (11) │ │ │ │ -9EA66 Version 01 (1) │ │ │ │ -9EA67 UID Size 04 (4) │ │ │ │ -9EA68 UID 00000000 (0) │ │ │ │ -9EA6C GID Size 04 (4) │ │ │ │ -9EA6D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EA71 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -9EA75 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EA76 Created OS 03 (3) 'Unix' │ │ │ │ -9EA77 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EA78 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EA79 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EA7B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EA7D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EA81 CRC 9F6AFEBF (2674589375) │ │ │ │ -9EA85 Compressed Size 000026C3 (9923) │ │ │ │ -9EA89 Uncompressed Size 00006E45 (28229) │ │ │ │ -9EA8D Filename Length 0019 (25) │ │ │ │ -9EA8F Extra Length 0018 (24) │ │ │ │ -9EA91 Comment Length 0000 (0) │ │ │ │ -9EA93 Disk Start 0000 (0) │ │ │ │ -9EA95 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EA97 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EA9B Local Header Offset 00049405 (300037) │ │ │ │ -9EA9F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EA9F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EAB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EABA Length 0005 (5) │ │ │ │ -9EABC Flags 01 (1) 'Modification' │ │ │ │ -9EABD Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EAC1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EAC3 Length 000B (11) │ │ │ │ -9EAC5 Version 01 (1) │ │ │ │ -9EAC6 UID Size 04 (4) │ │ │ │ -9EAC7 UID 00000000 (0) │ │ │ │ -9EACB GID Size 04 (4) │ │ │ │ -9EACC GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EAD0 CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -9EAD4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EAD5 Created OS 03 (3) 'Unix' │ │ │ │ -9EAD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EAD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EAD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EADA Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EADC Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EAE0 CRC 0369577C (57235324) │ │ │ │ -9EAE4 Compressed Size 00002739 (10041) │ │ │ │ -9EAE8 Uncompressed Size 00008B83 (35715) │ │ │ │ -9EAEC Filename Length 0019 (25) │ │ │ │ -9EAEE Extra Length 0018 (24) │ │ │ │ -9EAF0 Comment Length 0000 (0) │ │ │ │ -9EAF2 Disk Start 0000 (0) │ │ │ │ -9EAF4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EAF6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EAFA Local Header Offset 0004BB1B (310043) │ │ │ │ -9EAFE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EAFE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EB17 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EB19 Length 0005 (5) │ │ │ │ -9EB1B Flags 01 (1) 'Modification' │ │ │ │ -9EB1C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EB20 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EB22 Length 000B (11) │ │ │ │ -9EB24 Version 01 (1) │ │ │ │ -9EB25 UID Size 04 (4) │ │ │ │ -9EB26 UID 00000000 (0) │ │ │ │ -9EB2A GID Size 04 (4) │ │ │ │ -9EB2B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EB2F CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -9EB33 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EB34 Created OS 03 (3) 'Unix' │ │ │ │ -9EB35 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EB36 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EB37 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EB39 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EB3B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EB3F CRC 8B9BB6EF (2342237935) │ │ │ │ -9EB43 Compressed Size 00000CF1 (3313) │ │ │ │ -9EB47 Uncompressed Size 0000517A (20858) │ │ │ │ -9EB4B Filename Length 0021 (33) │ │ │ │ -9EB4D Extra Length 0018 (24) │ │ │ │ -9EB4F Comment Length 0000 (0) │ │ │ │ -9EB51 Disk Start 0000 (0) │ │ │ │ -9EB53 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EB55 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EB59 Local Header Offset 0004E2A7 (320167) │ │ │ │ -9EB5D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EB5D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EB7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EB80 Length 0005 (5) │ │ │ │ -9EB82 Flags 01 (1) 'Modification' │ │ │ │ -9EB83 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EB87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EB89 Length 000B (11) │ │ │ │ -9EB8B Version 01 (1) │ │ │ │ -9EB8C UID Size 04 (4) │ │ │ │ -9EB8D UID 00000000 (0) │ │ │ │ -9EB91 GID Size 04 (4) │ │ │ │ -9EB92 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EB96 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -9EB9A Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EB9B Created OS 03 (3) 'Unix' │ │ │ │ -9EB9C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EB9D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EB9E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EBA0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EBA2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EBA6 CRC 8F01E69C (2399266460) │ │ │ │ -9EBAA Compressed Size 00000468 (1128) │ │ │ │ -9EBAE Uncompressed Size 00000931 (2353) │ │ │ │ -9EBB2 Filename Length 001B (27) │ │ │ │ -9EBB4 Extra Length 0018 (24) │ │ │ │ -9EBB6 Comment Length 0000 (0) │ │ │ │ -9EBB8 Disk Start 0000 (0) │ │ │ │ -9EBBA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EBBC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EBC0 Local Header Offset 0004EFF3 (323571) │ │ │ │ -9EBC4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EBC4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EBDF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EBE1 Length 0005 (5) │ │ │ │ -9EBE3 Flags 01 (1) 'Modification' │ │ │ │ -9EBE4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EBE8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EBEA Length 000B (11) │ │ │ │ -9EBEC Version 01 (1) │ │ │ │ -9EBED UID Size 04 (4) │ │ │ │ -9EBEE UID 00000000 (0) │ │ │ │ -9EBF2 GID Size 04 (4) │ │ │ │ -9EBF3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EBF7 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -9EBFB Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EBFC Created OS 03 (3) 'Unix' │ │ │ │ -9EBFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EBFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EBFF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EC01 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EC03 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EC07 CRC 2A5112D6 (709956310) │ │ │ │ -9EC0B Compressed Size 000016F1 (5873) │ │ │ │ -9EC0F Uncompressed Size 00007A6D (31341) │ │ │ │ -9EC13 Filename Length 001F (31) │ │ │ │ -9EC15 Extra Length 0018 (24) │ │ │ │ -9EC17 Comment Length 0000 (0) │ │ │ │ -9EC19 Disk Start 0000 (0) │ │ │ │ -9EC1B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EC1D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EC21 Local Header Offset 0004F4B0 (324784) │ │ │ │ -9EC25 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EC25: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EC44 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EC46 Length 0005 (5) │ │ │ │ -9EC48 Flags 01 (1) 'Modification' │ │ │ │ -9EC49 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EC4D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EC4F Length 000B (11) │ │ │ │ -9EC51 Version 01 (1) │ │ │ │ -9EC52 UID Size 04 (4) │ │ │ │ -9EC53 UID 00000000 (0) │ │ │ │ -9EC57 GID Size 04 (4) │ │ │ │ -9EC58 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EC5C CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -9EC60 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EC61 Created OS 03 (3) 'Unix' │ │ │ │ -9EC62 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EC63 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EC64 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EC66 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EC68 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EC6C CRC 1BA8E4E8 (464053480) │ │ │ │ -9EC70 Compressed Size 0000416A (16746) │ │ │ │ -9EC74 Uncompressed Size 0001CF93 (118675) │ │ │ │ -9EC78 Filename Length 0010 (16) │ │ │ │ -9EC7A Extra Length 0018 (24) │ │ │ │ -9EC7C Comment Length 0000 (0) │ │ │ │ -9EC7E Disk Start 0000 (0) │ │ │ │ -9EC80 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EC82 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EC86 Local Header Offset 00050BFA (330746) │ │ │ │ -9EC8A Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EC8A: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EC9A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EC9C Length 0005 (5) │ │ │ │ -9EC9E Flags 01 (1) 'Modification' │ │ │ │ -9EC9F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9ECA3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9ECA5 Length 000B (11) │ │ │ │ -9ECA7 Version 01 (1) │ │ │ │ -9ECA8 UID Size 04 (4) │ │ │ │ -9ECA9 UID 00000000 (0) │ │ │ │ -9ECAD GID Size 04 (4) │ │ │ │ -9ECAE GID 00000000 (0) │ │ │ │ - │ │ │ │ -9ECB2 CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -9ECB6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9ECB7 Created OS 03 (3) 'Unix' │ │ │ │ -9ECB8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9ECB9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9ECBA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9ECBC Compression Method 0008 (8) 'Deflated' │ │ │ │ -9ECBE Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9ECC2 CRC 2020DAF6 (539024118) │ │ │ │ -9ECC6 Compressed Size 00000A94 (2708) │ │ │ │ -9ECCA Uncompressed Size 00002105 (8453) │ │ │ │ -9ECCE Filename Length 0014 (20) │ │ │ │ -9ECD0 Extra Length 0018 (24) │ │ │ │ -9ECD2 Comment Length 0000 (0) │ │ │ │ -9ECD4 Disk Start 0000 (0) │ │ │ │ -9ECD6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9ECD8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9ECDC Local Header Offset 00054DAE (347566) │ │ │ │ -9ECE0 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9ECE0: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9ECF4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9ECF6 Length 0005 (5) │ │ │ │ -9ECF8 Flags 01 (1) 'Modification' │ │ │ │ -9ECF9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9ECFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9ECFF Length 000B (11) │ │ │ │ -9ED01 Version 01 (1) │ │ │ │ -9ED02 UID Size 04 (4) │ │ │ │ -9ED03 UID 00000000 (0) │ │ │ │ -9ED07 GID Size 04 (4) │ │ │ │ -9ED08 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9ED0C CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -9ED10 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9ED11 Created OS 03 (3) 'Unix' │ │ │ │ -9ED12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9ED13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9ED14 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9ED16 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9ED18 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9ED1C CRC F1FD1FA8 (4059897768) │ │ │ │ -9ED20 Compressed Size 0000AC9D (44189) │ │ │ │ -9ED24 Uncompressed Size 0003E418 (255000) │ │ │ │ -9ED28 Filename Length 0017 (23) │ │ │ │ -9ED2A Extra Length 0018 (24) │ │ │ │ -9ED2C Comment Length 0000 (0) │ │ │ │ -9ED2E Disk Start 0000 (0) │ │ │ │ -9ED30 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9ED32 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9ED36 Local Header Offset 00055890 (350352) │ │ │ │ -9ED3A Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9ED3A: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9ED51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9ED53 Length 0005 (5) │ │ │ │ -9ED55 Flags 01 (1) 'Modification' │ │ │ │ -9ED56 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9ED5A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9ED5C Length 000B (11) │ │ │ │ -9ED5E Version 01 (1) │ │ │ │ -9ED5F UID Size 04 (4) │ │ │ │ -9ED60 UID 00000000 (0) │ │ │ │ -9ED64 GID Size 04 (4) │ │ │ │ -9ED65 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9ED69 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -9ED6D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9ED6E Created OS 03 (3) 'Unix' │ │ │ │ -9ED6F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9ED70 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9ED71 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9ED73 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9ED75 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9ED79 CRC 97230023 (2535653411) │ │ │ │ -9ED7D Compressed Size 00000401 (1025) │ │ │ │ -9ED81 Uncompressed Size 0000093D (2365) │ │ │ │ -9ED85 Filename Length 0013 (19) │ │ │ │ -9ED87 Extra Length 0018 (24) │ │ │ │ -9ED89 Comment Length 0000 (0) │ │ │ │ -9ED8B Disk Start 0000 (0) │ │ │ │ -9ED8D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9ED8F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9ED93 Local Header Offset 0006057E (394622) │ │ │ │ -9ED97 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9ED97: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EDAA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EDAC Length 0005 (5) │ │ │ │ -9EDAE Flags 01 (1) 'Modification' │ │ │ │ -9EDAF Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EDB3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EDB5 Length 000B (11) │ │ │ │ -9EDB7 Version 01 (1) │ │ │ │ -9EDB8 UID Size 04 (4) │ │ │ │ -9EDB9 UID 00000000 (0) │ │ │ │ -9EDBD GID Size 04 (4) │ │ │ │ -9EDBE GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EDC2 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -9EDC6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EDC7 Created OS 03 (3) 'Unix' │ │ │ │ -9EDC8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EDC9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EDCA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EDCC Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EDCE Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EDD2 CRC 15AD4D8E (363679118) │ │ │ │ -9EDD6 Compressed Size 000014DB (5339) │ │ │ │ -9EDDA Uncompressed Size 0000687B (26747) │ │ │ │ -9EDDE Filename Length 0012 (18) │ │ │ │ -9EDE0 Extra Length 0018 (24) │ │ │ │ -9EDE2 Comment Length 0000 (0) │ │ │ │ -9EDE4 Disk Start 0000 (0) │ │ │ │ -9EDE6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EDE8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EDEC Local Header Offset 000609CC (395724) │ │ │ │ -9EDF0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EDF0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EE02 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EE04 Length 0005 (5) │ │ │ │ -9EE06 Flags 01 (1) 'Modification' │ │ │ │ -9EE07 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EE0B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EE0D Length 000B (11) │ │ │ │ -9EE0F Version 01 (1) │ │ │ │ -9EE10 UID Size 04 (4) │ │ │ │ -9EE11 UID 00000000 (0) │ │ │ │ -9EE15 GID Size 04 (4) │ │ │ │ -9EE16 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EE1A CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -9EE1E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EE1F Created OS 03 (3) 'Unix' │ │ │ │ -9EE20 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EE21 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EE22 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EE24 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EE26 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EE2A CRC BDF3884F (3186853967) │ │ │ │ -9EE2E Compressed Size 000011EB (4587) │ │ │ │ -9EE32 Uncompressed Size 000040F5 (16629) │ │ │ │ -9EE36 Filename Length 0012 (18) │ │ │ │ -9EE38 Extra Length 0018 (24) │ │ │ │ -9EE3A Comment Length 0000 (0) │ │ │ │ -9EE3C Disk Start 0000 (0) │ │ │ │ -9EE3E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EE40 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EE44 Local Header Offset 00061EF3 (401139) │ │ │ │ -9EE48 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EE48: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EE5A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EE5C Length 0005 (5) │ │ │ │ -9EE5E Flags 01 (1) 'Modification' │ │ │ │ -9EE5F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EE63 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EE65 Length 000B (11) │ │ │ │ -9EE67 Version 01 (1) │ │ │ │ -9EE68 UID Size 04 (4) │ │ │ │ -9EE69 UID 00000000 (0) │ │ │ │ -9EE6D GID Size 04 (4) │ │ │ │ -9EE6E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EE72 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -9EE76 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EE77 Created OS 03 (3) 'Unix' │ │ │ │ -9EE78 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EE79 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EE7A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EE7C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EE7E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EE82 CRC 2C7ABAF2 (746240754) │ │ │ │ -9EE86 Compressed Size 000009DA (2522) │ │ │ │ -9EE8A Uncompressed Size 00003529 (13609) │ │ │ │ -9EE8E Filename Length 0019 (25) │ │ │ │ -9EE90 Extra Length 0018 (24) │ │ │ │ -9EE92 Comment Length 0000 (0) │ │ │ │ -9EE94 Disk Start 0000 (0) │ │ │ │ -9EE96 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EE98 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EE9C Local Header Offset 0006312A (405802) │ │ │ │ -9EEA0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EEA0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EEB9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EEBB Length 0005 (5) │ │ │ │ -9EEBD Flags 01 (1) 'Modification' │ │ │ │ -9EEBE Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EEC2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EEC4 Length 000B (11) │ │ │ │ -9EEC6 Version 01 (1) │ │ │ │ -9EEC7 UID Size 04 (4) │ │ │ │ -9EEC8 UID 00000000 (0) │ │ │ │ -9EECC GID Size 04 (4) │ │ │ │ -9EECD GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EED1 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -9EED5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EED6 Created OS 03 (3) 'Unix' │ │ │ │ -9EED7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EED8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EED9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EEDB Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EEDD Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EEE1 CRC D69E8D28 (3600715048) │ │ │ │ -9EEE5 Compressed Size 000018B1 (6321) │ │ │ │ -9EEE9 Uncompressed Size 0000A605 (42501) │ │ │ │ -9EEED Filename Length 0019 (25) │ │ │ │ -9EEEF Extra Length 0018 (24) │ │ │ │ -9EEF1 Comment Length 0000 (0) │ │ │ │ -9EEF3 Disk Start 0000 (0) │ │ │ │ -9EEF5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EEF7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EEFB Local Header Offset 00063B57 (408407) │ │ │ │ -9EEFF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EEFF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EF18 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EF1A Length 0005 (5) │ │ │ │ -9EF1C Flags 01 (1) 'Modification' │ │ │ │ -9EF1D Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EF21 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EF23 Length 000B (11) │ │ │ │ -9EF25 Version 01 (1) │ │ │ │ -9EF26 UID Size 04 (4) │ │ │ │ -9EF27 UID 00000000 (0) │ │ │ │ -9EF2B GID Size 04 (4) │ │ │ │ -9EF2C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EF30 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -9EF34 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EF35 Created OS 03 (3) 'Unix' │ │ │ │ -9EF36 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EF37 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EF38 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EF3A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EF3C Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EF40 CRC 47E0D6DC (1205917404) │ │ │ │ -9EF44 Compressed Size 0000177E (6014) │ │ │ │ -9EF48 Uncompressed Size 0000472C (18220) │ │ │ │ -9EF4C Filename Length 0014 (20) │ │ │ │ -9EF4E Extra Length 0018 (24) │ │ │ │ -9EF50 Comment Length 0000 (0) │ │ │ │ -9EF52 Disk Start 0000 (0) │ │ │ │ -9EF54 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EF56 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EF5A Local Header Offset 0006545B (414811) │ │ │ │ -9EF5E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EF5E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EF72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EF74 Length 0005 (5) │ │ │ │ -9EF76 Flags 01 (1) 'Modification' │ │ │ │ -9EF77 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EF7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EF7D Length 000B (11) │ │ │ │ -9EF7F Version 01 (1) │ │ │ │ -9EF80 UID Size 04 (4) │ │ │ │ -9EF81 UID 00000000 (0) │ │ │ │ -9EF85 GID Size 04 (4) │ │ │ │ -9EF86 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EF8A CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -9EF8E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EF8F Created OS 03 (3) 'Unix' │ │ │ │ -9EF90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EF91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EF92 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EF94 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EF96 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EF9A CRC EDF51269 (3992261225) │ │ │ │ -9EF9E Compressed Size 0000040B (1035) │ │ │ │ -9EFA2 Uncompressed Size 00000825 (2085) │ │ │ │ -9EFA6 Filename Length 001C (28) │ │ │ │ -9EFA8 Extra Length 0018 (24) │ │ │ │ -9EFAA Comment Length 0000 (0) │ │ │ │ -9EFAC Disk Start 0000 (0) │ │ │ │ -9EFAE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9EFB0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9EFB4 Local Header Offset 00066C27 (420903) │ │ │ │ -9EFB8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9EFB8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9EFD4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9EFD6 Length 0005 (5) │ │ │ │ -9EFD8 Flags 01 (1) 'Modification' │ │ │ │ -9EFD9 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9EFDD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9EFDF Length 000B (11) │ │ │ │ -9EFE1 Version 01 (1) │ │ │ │ -9EFE2 UID Size 04 (4) │ │ │ │ -9EFE3 UID 00000000 (0) │ │ │ │ -9EFE7 GID Size 04 (4) │ │ │ │ -9EFE8 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9EFEC CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -9EFF0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9EFF1 Created OS 03 (3) 'Unix' │ │ │ │ -9EFF2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9EFF3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9EFF4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9EFF6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9EFF8 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9EFFC CRC AC935B99 (2895338393) │ │ │ │ -9F000 Compressed Size 00002483 (9347) │ │ │ │ -9F004 Uncompressed Size 0000B56F (46447) │ │ │ │ -9F008 Filename Length 001F (31) │ │ │ │ -9F00A Extra Length 0018 (24) │ │ │ │ -9F00C Comment Length 0000 (0) │ │ │ │ -9F00E Disk Start 0000 (0) │ │ │ │ -9F010 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F012 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F016 Local Header Offset 00067088 (422024) │ │ │ │ -9F01A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F01A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F039 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F03B Length 0005 (5) │ │ │ │ -9F03D Flags 01 (1) 'Modification' │ │ │ │ -9F03E Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F042 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F044 Length 000B (11) │ │ │ │ -9F046 Version 01 (1) │ │ │ │ -9F047 UID Size 04 (4) │ │ │ │ -9F048 UID 00000000 (0) │ │ │ │ -9F04C GID Size 04 (4) │ │ │ │ -9F04D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F051 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -9F055 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F056 Created OS 03 (3) 'Unix' │ │ │ │ -9F057 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F058 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F059 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F05B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F05D Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F061 CRC EE677FF5 (3999760373) │ │ │ │ -9F065 Compressed Size 00000E81 (3713) │ │ │ │ -9F069 Uncompressed Size 000052D9 (21209) │ │ │ │ -9F06D Filename Length 001F (31) │ │ │ │ -9F06F Extra Length 0018 (24) │ │ │ │ -9F071 Comment Length 0000 (0) │ │ │ │ -9F073 Disk Start 0000 (0) │ │ │ │ -9F075 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F077 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F07B Local Header Offset 00069564 (431460) │ │ │ │ -9F07F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F07F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F09E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F0A0 Length 0005 (5) │ │ │ │ -9F0A2 Flags 01 (1) 'Modification' │ │ │ │ -9F0A3 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F0A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F0A9 Length 000B (11) │ │ │ │ -9F0AB Version 01 (1) │ │ │ │ -9F0AC UID Size 04 (4) │ │ │ │ -9F0AD UID 00000000 (0) │ │ │ │ -9F0B1 GID Size 04 (4) │ │ │ │ -9F0B2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F0B6 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -9F0BA Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F0BB Created OS 03 (3) 'Unix' │ │ │ │ -9F0BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F0BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F0BE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F0C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F0C2 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F0C6 CRC 651C981F (1696372767) │ │ │ │ -9F0CA Compressed Size 00000A43 (2627) │ │ │ │ -9F0CE Uncompressed Size 0000247A (9338) │ │ │ │ -9F0D2 Filename Length 0013 (19) │ │ │ │ -9F0D4 Extra Length 0018 (24) │ │ │ │ -9F0D6 Comment Length 0000 (0) │ │ │ │ -9F0D8 Disk Start 0000 (0) │ │ │ │ -9F0DA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F0DC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F0E0 Local Header Offset 0006A43E (435262) │ │ │ │ -9F0E4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F0E4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F0F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F0F9 Length 0005 (5) │ │ │ │ -9F0FB Flags 01 (1) 'Modification' │ │ │ │ -9F0FC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F100 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F102 Length 000B (11) │ │ │ │ -9F104 Version 01 (1) │ │ │ │ -9F105 UID Size 04 (4) │ │ │ │ -9F106 UID 00000000 (0) │ │ │ │ -9F10A GID Size 04 (4) │ │ │ │ -9F10B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F10F CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -9F113 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F114 Created OS 03 (3) 'Unix' │ │ │ │ -9F115 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F116 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F117 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F119 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F11B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F11F CRC 6A1CF0DA (1780281562) │ │ │ │ -9F123 Compressed Size 00002486 (9350) │ │ │ │ -9F127 Uncompressed Size 0000B84C (47180) │ │ │ │ -9F12B Filename Length 0019 (25) │ │ │ │ -9F12D Extra Length 0018 (24) │ │ │ │ -9F12F Comment Length 0000 (0) │ │ │ │ -9F131 Disk Start 0000 (0) │ │ │ │ -9F133 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F135 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F139 Local Header Offset 0006AECE (437966) │ │ │ │ -9F13D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F13D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F156 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F158 Length 0005 (5) │ │ │ │ -9F15A Flags 01 (1) 'Modification' │ │ │ │ -9F15B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F15F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F161 Length 000B (11) │ │ │ │ -9F163 Version 01 (1) │ │ │ │ -9F164 UID Size 04 (4) │ │ │ │ -9F165 UID 00000000 (0) │ │ │ │ -9F169 GID Size 04 (4) │ │ │ │ -9F16A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F16E CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -9F172 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F173 Created OS 03 (3) 'Unix' │ │ │ │ -9F174 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F175 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F176 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F178 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F17A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F17E CRC E9583C9A (3914874010) │ │ │ │ -9F182 Compressed Size 00000EF9 (3833) │ │ │ │ -9F186 Uncompressed Size 00003A2C (14892) │ │ │ │ -9F18A Filename Length 0024 (36) │ │ │ │ -9F18C Extra Length 0018 (24) │ │ │ │ -9F18E Comment Length 0000 (0) │ │ │ │ -9F190 Disk Start 0000 (0) │ │ │ │ -9F192 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F194 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F198 Local Header Offset 0006D3A7 (447399) │ │ │ │ -9F19C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F19C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F1C0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F1C2 Length 0005 (5) │ │ │ │ -9F1C4 Flags 01 (1) 'Modification' │ │ │ │ -9F1C5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F1C9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F1CB Length 000B (11) │ │ │ │ -9F1CD Version 01 (1) │ │ │ │ -9F1CE UID Size 04 (4) │ │ │ │ -9F1CF UID 00000000 (0) │ │ │ │ -9F1D3 GID Size 04 (4) │ │ │ │ -9F1D4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F1D8 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -9F1DC Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F1DD Created OS 03 (3) 'Unix' │ │ │ │ -9F1DE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F1DF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F1E0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F1E2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F1E4 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F1E8 CRC 0D165AC6 (219568838) │ │ │ │ -9F1EC Compressed Size 00001AC1 (6849) │ │ │ │ -9F1F0 Uncompressed Size 00005EDC (24284) │ │ │ │ -9F1F4 Filename Length 0017 (23) │ │ │ │ -9F1F6 Extra Length 0018 (24) │ │ │ │ -9F1F8 Comment Length 0000 (0) │ │ │ │ -9F1FA Disk Start 0000 (0) │ │ │ │ -9F1FC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F1FE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F202 Local Header Offset 0006E2FE (451326) │ │ │ │ -9F206 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F206: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F21D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F21F Length 0005 (5) │ │ │ │ -9F221 Flags 01 (1) 'Modification' │ │ │ │ -9F222 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F226 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F228 Length 000B (11) │ │ │ │ -9F22A Version 01 (1) │ │ │ │ -9F22B UID Size 04 (4) │ │ │ │ -9F22C UID 00000000 (0) │ │ │ │ -9F230 GID Size 04 (4) │ │ │ │ -9F231 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F235 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -9F239 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F23A Created OS 03 (3) 'Unix' │ │ │ │ -9F23B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F23C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F23D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F23F Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F241 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F245 CRC 11E32AF1 (300100337) │ │ │ │ -9F249 Compressed Size 00000ED3 (3795) │ │ │ │ -9F24D Uncompressed Size 000038E2 (14562) │ │ │ │ -9F251 Filename Length 0023 (35) │ │ │ │ -9F253 Extra Length 0018 (24) │ │ │ │ -9F255 Comment Length 0000 (0) │ │ │ │ -9F257 Disk Start 0000 (0) │ │ │ │ -9F259 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F25B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F25F Local Header Offset 0006FE10 (458256) │ │ │ │ -9F263 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F263: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F286 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F288 Length 0005 (5) │ │ │ │ -9F28A Flags 01 (1) 'Modification' │ │ │ │ -9F28B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F28F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F291 Length 000B (11) │ │ │ │ -9F293 Version 01 (1) │ │ │ │ -9F294 UID Size 04 (4) │ │ │ │ -9F295 UID 00000000 (0) │ │ │ │ -9F299 GID Size 04 (4) │ │ │ │ -9F29A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F29E CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -9F2A2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F2A3 Created OS 03 (3) 'Unix' │ │ │ │ -9F2A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F2A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F2A6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F2A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F2AA Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F2AE CRC 2DB7929F (767005343) │ │ │ │ -9F2B2 Compressed Size 00000113 (275) │ │ │ │ -9F2B6 Uncompressed Size 000001F3 (499) │ │ │ │ -9F2BA Filename Length 001B (27) │ │ │ │ -9F2BC Extra Length 0018 (24) │ │ │ │ -9F2BE Comment Length 0000 (0) │ │ │ │ -9F2C0 Disk Start 0000 (0) │ │ │ │ -9F2C2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F2C4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F2C8 Local Header Offset 00070D40 (462144) │ │ │ │ -9F2CC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F2CC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F2E7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F2E9 Length 0005 (5) │ │ │ │ -9F2EB Flags 01 (1) 'Modification' │ │ │ │ -9F2EC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F2F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F2F2 Length 000B (11) │ │ │ │ -9F2F4 Version 01 (1) │ │ │ │ -9F2F5 UID Size 04 (4) │ │ │ │ -9F2F6 UID 00000000 (0) │ │ │ │ -9F2FA GID Size 04 (4) │ │ │ │ -9F2FB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F2FF CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -9F303 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F304 Created OS 03 (3) 'Unix' │ │ │ │ -9F305 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F306 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F307 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F309 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F30B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F30F CRC BC4620C3 (3158712515) │ │ │ │ -9F313 Compressed Size 00001892 (6290) │ │ │ │ -9F317 Uncompressed Size 00008FAC (36780) │ │ │ │ -9F31B Filename Length 001D (29) │ │ │ │ -9F31D Extra Length 0018 (24) │ │ │ │ -9F31F Comment Length 0000 (0) │ │ │ │ -9F321 Disk Start 0000 (0) │ │ │ │ -9F323 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F325 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F329 Local Header Offset 00070EA8 (462504) │ │ │ │ -9F32D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F32D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F34A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F34C Length 0005 (5) │ │ │ │ -9F34E Flags 01 (1) 'Modification' │ │ │ │ -9F34F Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F353 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F355 Length 000B (11) │ │ │ │ -9F357 Version 01 (1) │ │ │ │ -9F358 UID Size 04 (4) │ │ │ │ -9F359 UID 00000000 (0) │ │ │ │ -9F35D GID Size 04 (4) │ │ │ │ -9F35E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F362 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -9F366 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F367 Created OS 03 (3) 'Unix' │ │ │ │ -9F368 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F369 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F36A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F36C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F36E Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F372 CRC 63FED801 (1677645825) │ │ │ │ -9F376 Compressed Size 0000164D (5709) │ │ │ │ -9F37A Uncompressed Size 00003A9B (15003) │ │ │ │ -9F37E Filename Length 0015 (21) │ │ │ │ -9F380 Extra Length 0018 (24) │ │ │ │ -9F382 Comment Length 0000 (0) │ │ │ │ -9F384 Disk Start 0000 (0) │ │ │ │ -9F386 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F388 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F38C Local Header Offset 00072791 (468881) │ │ │ │ -9F390 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F390: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F3A5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F3A7 Length 0005 (5) │ │ │ │ -9F3A9 Flags 01 (1) 'Modification' │ │ │ │ -9F3AA Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F3AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F3B0 Length 000B (11) │ │ │ │ -9F3B2 Version 01 (1) │ │ │ │ -9F3B3 UID Size 04 (4) │ │ │ │ -9F3B4 UID 00000000 (0) │ │ │ │ -9F3B8 GID Size 04 (4) │ │ │ │ -9F3B9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F3BD CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -9F3C1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F3C2 Created OS 03 (3) 'Unix' │ │ │ │ -9F3C3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F3C4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F3C5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F3C7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F3C9 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F3CD CRC ADE907F6 (2917730294) │ │ │ │ -9F3D1 Compressed Size 00003B50 (15184) │ │ │ │ -9F3D5 Uncompressed Size 0001185B (71771) │ │ │ │ -9F3D9 Filename Length 0016 (22) │ │ │ │ -9F3DB Extra Length 0018 (24) │ │ │ │ -9F3DD Comment Length 0000 (0) │ │ │ │ -9F3DF Disk Start 0000 (0) │ │ │ │ -9F3E1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F3E3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F3E7 Local Header Offset 00073E2D (474669) │ │ │ │ -9F3EB Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F3EB: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F401 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F403 Length 0005 (5) │ │ │ │ -9F405 Flags 01 (1) 'Modification' │ │ │ │ -9F406 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F40A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F40C Length 000B (11) │ │ │ │ -9F40E Version 01 (1) │ │ │ │ -9F40F UID Size 04 (4) │ │ │ │ -9F410 UID 00000000 (0) │ │ │ │ -9F414 GID Size 04 (4) │ │ │ │ -9F415 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F419 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -9F41D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F41E Created OS 03 (3) 'Unix' │ │ │ │ -9F41F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F420 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F421 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F423 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F425 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F429 CRC 7DF00FFC (2112884732) │ │ │ │ -9F42D Compressed Size 00003E85 (16005) │ │ │ │ -9F431 Uncompressed Size 0001C17B (115067) │ │ │ │ -9F435 Filename Length 0019 (25) │ │ │ │ -9F437 Extra Length 0018 (24) │ │ │ │ -9F439 Comment Length 0000 (0) │ │ │ │ -9F43B Disk Start 0000 (0) │ │ │ │ -9F43D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F43F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F443 Local Header Offset 000779CD (489933) │ │ │ │ -9F447 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F447: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F460 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F462 Length 0005 (5) │ │ │ │ -9F464 Flags 01 (1) 'Modification' │ │ │ │ -9F465 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F469 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F46B Length 000B (11) │ │ │ │ -9F46D Version 01 (1) │ │ │ │ -9F46E UID Size 04 (4) │ │ │ │ -9F46F UID 00000000 (0) │ │ │ │ -9F473 GID Size 04 (4) │ │ │ │ -9F474 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F478 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -9F47C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F47D Created OS 03 (3) 'Unix' │ │ │ │ -9F47E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F47F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F480 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F482 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F484 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F488 CRC 08716215 (141648405) │ │ │ │ -9F48C Compressed Size 00000833 (2099) │ │ │ │ -9F490 Uncompressed Size 00003383 (13187) │ │ │ │ -9F494 Filename Length 0011 (17) │ │ │ │ -9F496 Extra Length 0018 (24) │ │ │ │ -9F498 Comment Length 0000 (0) │ │ │ │ -9F49A Disk Start 0000 (0) │ │ │ │ -9F49C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F49E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F4A2 Local Header Offset 0007B8A5 (506021) │ │ │ │ -9F4A6 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F4A6: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F4B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F4B9 Length 0005 (5) │ │ │ │ -9F4BB Flags 01 (1) 'Modification' │ │ │ │ -9F4BC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F4C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F4C2 Length 000B (11) │ │ │ │ -9F4C4 Version 01 (1) │ │ │ │ -9F4C5 UID Size 04 (4) │ │ │ │ -9F4C6 UID 00000000 (0) │ │ │ │ -9F4CA GID Size 04 (4) │ │ │ │ -9F4CB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F4CF CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -9F4D3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F4D4 Created OS 03 (3) 'Unix' │ │ │ │ -9F4D5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F4D6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F4D7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F4D9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F4DB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F4DF CRC 9B8ACD82 (2609565058) │ │ │ │ -9F4E3 Compressed Size 00005188 (20872) │ │ │ │ -9F4E7 Uncompressed Size 0001FB6C (129900) │ │ │ │ -9F4EB Filename Length 0015 (21) │ │ │ │ -9F4ED Extra Length 0018 (24) │ │ │ │ -9F4EF Comment Length 0000 (0) │ │ │ │ -9F4F1 Disk Start 0000 (0) │ │ │ │ -9F4F3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F4F5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F4F9 Local Header Offset 0007C123 (508195) │ │ │ │ -9F4FD Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F4FD: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F512 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F514 Length 0005 (5) │ │ │ │ -9F516 Flags 01 (1) 'Modification' │ │ │ │ -9F517 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F51B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F51D Length 000B (11) │ │ │ │ -9F51F Version 01 (1) │ │ │ │ -9F520 UID Size 04 (4) │ │ │ │ -9F521 UID 00000000 (0) │ │ │ │ -9F525 GID Size 04 (4) │ │ │ │ -9F526 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F52A CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -9F52E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F52F Created OS 03 (3) 'Unix' │ │ │ │ -9F530 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F531 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F532 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F534 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F536 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F53A CRC 535E57D6 (1398691798) │ │ │ │ -9F53E Compressed Size 00001B05 (6917) │ │ │ │ -9F542 Uncompressed Size 000081CF (33231) │ │ │ │ -9F546 Filename Length 0019 (25) │ │ │ │ -9F548 Extra Length 0018 (24) │ │ │ │ -9F54A Comment Length 0000 (0) │ │ │ │ -9F54C Disk Start 0000 (0) │ │ │ │ -9F54E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F550 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F554 Local Header Offset 000812FA (529146) │ │ │ │ -9F558 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F558: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F571 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F573 Length 0005 (5) │ │ │ │ -9F575 Flags 01 (1) 'Modification' │ │ │ │ -9F576 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F57A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F57C Length 000B (11) │ │ │ │ -9F57E Version 01 (1) │ │ │ │ -9F57F UID Size 04 (4) │ │ │ │ -9F580 UID 00000000 (0) │ │ │ │ -9F584 GID Size 04 (4) │ │ │ │ -9F585 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F589 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -9F58D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F58E Created OS 03 (3) 'Unix' │ │ │ │ -9F58F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F590 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F591 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F593 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F595 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F599 CRC 02D5C73B (47564603) │ │ │ │ -9F59D Compressed Size 00000D96 (3478) │ │ │ │ -9F5A1 Uncompressed Size 00002E9F (11935) │ │ │ │ -9F5A5 Filename Length 0018 (24) │ │ │ │ -9F5A7 Extra Length 0018 (24) │ │ │ │ -9F5A9 Comment Length 0000 (0) │ │ │ │ -9F5AB Disk Start 0000 (0) │ │ │ │ -9F5AD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F5AF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F5B3 Local Header Offset 00082E52 (536146) │ │ │ │ -9F5B7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F5B7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F5CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F5D1 Length 0005 (5) │ │ │ │ -9F5D3 Flags 01 (1) 'Modification' │ │ │ │ -9F5D4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F5D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F5DA Length 000B (11) │ │ │ │ -9F5DC Version 01 (1) │ │ │ │ -9F5DD UID Size 04 (4) │ │ │ │ -9F5DE UID 00000000 (0) │ │ │ │ -9F5E2 GID Size 04 (4) │ │ │ │ -9F5E3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F5E7 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -9F5EB Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F5EC Created OS 03 (3) 'Unix' │ │ │ │ -9F5ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F5EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F5EF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F5F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F5F3 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F5F7 CRC B429472D (3022604077) │ │ │ │ -9F5FB Compressed Size 000001E1 (481) │ │ │ │ -9F5FF Uncompressed Size 00000323 (803) │ │ │ │ -9F603 Filename Length 0011 (17) │ │ │ │ -9F605 Extra Length 0018 (24) │ │ │ │ -9F607 Comment Length 0000 (0) │ │ │ │ -9F609 Disk Start 0000 (0) │ │ │ │ -9F60B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F60D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F611 Local Header Offset 00083C3A (539706) │ │ │ │ -9F615 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F615: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F626 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F628 Length 0005 (5) │ │ │ │ -9F62A Flags 01 (1) 'Modification' │ │ │ │ -9F62B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F62F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F631 Length 000B (11) │ │ │ │ -9F633 Version 01 (1) │ │ │ │ -9F634 UID Size 04 (4) │ │ │ │ -9F635 UID 00000000 (0) │ │ │ │ -9F639 GID Size 04 (4) │ │ │ │ -9F63A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F63E CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -9F642 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F643 Created OS 03 (3) 'Unix' │ │ │ │ -9F644 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F645 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F646 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F648 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F64A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F64E CRC 1E45F0E3 (507900131) │ │ │ │ -9F652 Compressed Size 000006C1 (1729) │ │ │ │ -9F656 Uncompressed Size 00001439 (5177) │ │ │ │ -9F65A Filename Length 0019 (25) │ │ │ │ -9F65C Extra Length 0018 (24) │ │ │ │ -9F65E Comment Length 0000 (0) │ │ │ │ -9F660 Disk Start 0000 (0) │ │ │ │ -9F662 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F664 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F668 Local Header Offset 00083E66 (540262) │ │ │ │ -9F66C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F66C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F685 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F687 Length 0005 (5) │ │ │ │ -9F689 Flags 01 (1) 'Modification' │ │ │ │ -9F68A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F68E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F690 Length 000B (11) │ │ │ │ -9F692 Version 01 (1) │ │ │ │ -9F693 UID Size 04 (4) │ │ │ │ -9F694 UID 00000000 (0) │ │ │ │ -9F698 GID Size 04 (4) │ │ │ │ -9F699 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F69D CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -9F6A1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F6A2 Created OS 03 (3) 'Unix' │ │ │ │ -9F6A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F6A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F6A5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F6A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F6A9 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F6AD CRC 31106466 (823157862) │ │ │ │ -9F6B1 Compressed Size 00001B86 (7046) │ │ │ │ -9F6B5 Uncompressed Size 00009F03 (40707) │ │ │ │ -9F6B9 Filename Length 0018 (24) │ │ │ │ -9F6BB Extra Length 0018 (24) │ │ │ │ -9F6BD Comment Length 0000 (0) │ │ │ │ -9F6BF Disk Start 0000 (0) │ │ │ │ -9F6C1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F6C3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F6C7 Local Header Offset 0008457A (542074) │ │ │ │ -9F6CB Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F6CB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F6E3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F6E5 Length 0005 (5) │ │ │ │ -9F6E7 Flags 01 (1) 'Modification' │ │ │ │ -9F6E8 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F6EC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F6EE Length 000B (11) │ │ │ │ -9F6F0 Version 01 (1) │ │ │ │ -9F6F1 UID Size 04 (4) │ │ │ │ -9F6F2 UID 00000000 (0) │ │ │ │ -9F6F6 GID Size 04 (4) │ │ │ │ -9F6F7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F6FB CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -9F6FF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F700 Created OS 03 (3) 'Unix' │ │ │ │ -9F701 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F702 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F703 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F705 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F707 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F70B CRC EAA681B2 (3936780722) │ │ │ │ -9F70F Compressed Size 000016FB (5883) │ │ │ │ -9F713 Uncompressed Size 00008AB6 (35510) │ │ │ │ -9F717 Filename Length 0012 (18) │ │ │ │ -9F719 Extra Length 0018 (24) │ │ │ │ -9F71B Comment Length 0000 (0) │ │ │ │ -9F71D Disk Start 0000 (0) │ │ │ │ -9F71F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F721 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F725 Local Header Offset 00086152 (549202) │ │ │ │ -9F729 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F729: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F73B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F73D Length 0005 (5) │ │ │ │ -9F73F Flags 01 (1) 'Modification' │ │ │ │ -9F740 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F744 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F746 Length 000B (11) │ │ │ │ -9F748 Version 01 (1) │ │ │ │ -9F749 UID Size 04 (4) │ │ │ │ -9F74A UID 00000000 (0) │ │ │ │ -9F74E GID Size 04 (4) │ │ │ │ -9F74F GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F753 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -9F757 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F758 Created OS 03 (3) 'Unix' │ │ │ │ -9F759 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F75A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F75B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F75D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F75F Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F763 CRC 2B5B6715 (727410453) │ │ │ │ -9F767 Compressed Size 00001E12 (7698) │ │ │ │ -9F76B Uncompressed Size 00008803 (34819) │ │ │ │ -9F76F Filename Length 0016 (22) │ │ │ │ -9F771 Extra Length 0018 (24) │ │ │ │ -9F773 Comment Length 0000 (0) │ │ │ │ -9F775 Disk Start 0000 (0) │ │ │ │ -9F777 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F779 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F77D Local Header Offset 00087899 (555161) │ │ │ │ -9F781 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F781: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F797 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F799 Length 0005 (5) │ │ │ │ -9F79B Flags 01 (1) 'Modification' │ │ │ │ -9F79C Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F7A0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F7A2 Length 000B (11) │ │ │ │ -9F7A4 Version 01 (1) │ │ │ │ -9F7A5 UID Size 04 (4) │ │ │ │ -9F7A6 UID 00000000 (0) │ │ │ │ -9F7AA GID Size 04 (4) │ │ │ │ -9F7AB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F7AF CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -9F7B3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F7B4 Created OS 03 (3) 'Unix' │ │ │ │ -9F7B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F7B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F7B7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F7B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F7BB Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F7BF CRC F1BBDD3F (4055620927) │ │ │ │ -9F7C3 Compressed Size 000029A9 (10665) │ │ │ │ -9F7C7 Uncompressed Size 0000D04F (53327) │ │ │ │ -9F7CB Filename Length 001A (26) │ │ │ │ -9F7CD Extra Length 0018 (24) │ │ │ │ -9F7CF Comment Length 0000 (0) │ │ │ │ -9F7D1 Disk Start 0000 (0) │ │ │ │ -9F7D3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F7D5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F7D9 Local Header Offset 000896FB (562939) │ │ │ │ -9F7DD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F7DD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F7F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F7F9 Length 0005 (5) │ │ │ │ -9F7FB Flags 01 (1) 'Modification' │ │ │ │ -9F7FC Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F800 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F802 Length 000B (11) │ │ │ │ -9F804 Version 01 (1) │ │ │ │ -9F805 UID Size 04 (4) │ │ │ │ -9F806 UID 00000000 (0) │ │ │ │ -9F80A GID Size 04 (4) │ │ │ │ -9F80B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F80F CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -9F813 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F814 Created OS 03 (3) 'Unix' │ │ │ │ -9F815 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F816 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F817 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F819 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F81B Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F81F CRC DC838374 (3699606388) │ │ │ │ -9F823 Compressed Size 000009AB (2475) │ │ │ │ -9F827 Uncompressed Size 00001DB6 (7606) │ │ │ │ -9F82B Filename Length 0018 (24) │ │ │ │ -9F82D Extra Length 0018 (24) │ │ │ │ -9F82F Comment Length 0000 (0) │ │ │ │ -9F831 Disk Start 0000 (0) │ │ │ │ -9F833 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F835 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F839 Local Header Offset 0008C0F8 (573688) │ │ │ │ -9F83D Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F83D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F855 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F857 Length 0005 (5) │ │ │ │ -9F859 Flags 01 (1) 'Modification' │ │ │ │ -9F85A Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F85E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F860 Length 000B (11) │ │ │ │ -9F862 Version 01 (1) │ │ │ │ -9F863 UID Size 04 (4) │ │ │ │ -9F864 UID 00000000 (0) │ │ │ │ -9F868 GID Size 04 (4) │ │ │ │ -9F869 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F86D CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -9F871 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F872 Created OS 03 (3) 'Unix' │ │ │ │ -9F873 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F874 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F875 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F877 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F879 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F87D CRC F5E2129F (4125233823) │ │ │ │ -9F881 Compressed Size 000016BC (5820) │ │ │ │ -9F885 Uncompressed Size 000016CD (5837) │ │ │ │ -9F889 Filename Length 0015 (21) │ │ │ │ -9F88B Extra Length 0018 (24) │ │ │ │ -9F88D Comment Length 0000 (0) │ │ │ │ -9F88F Disk Start 0000 (0) │ │ │ │ -9F891 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F893 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F897 Local Header Offset 0008CAF5 (576245) │ │ │ │ -9F89B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F89B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F8B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F8B2 Length 0005 (5) │ │ │ │ -9F8B4 Flags 01 (1) 'Modification' │ │ │ │ -9F8B5 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F8B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F8BB Length 000B (11) │ │ │ │ -9F8BD Version 01 (1) │ │ │ │ -9F8BE UID Size 04 (4) │ │ │ │ -9F8BF UID 00000000 (0) │ │ │ │ -9F8C3 GID Size 04 (4) │ │ │ │ -9F8C4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F8C8 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -9F8CC Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F8CD Created OS 03 (3) 'Unix' │ │ │ │ -9F8CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F8CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F8D0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F8D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F8D4 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F8D8 CRC F5E2129F (4125233823) │ │ │ │ -9F8DC Compressed Size 000016BC (5820) │ │ │ │ -9F8E0 Uncompressed Size 000016CD (5837) │ │ │ │ -9F8E4 Filename Length 001C (28) │ │ │ │ -9F8E6 Extra Length 0018 (24) │ │ │ │ -9F8E8 Comment Length 0000 (0) │ │ │ │ -9F8EA Disk Start 0000 (0) │ │ │ │ -9F8EC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F8EE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F8F2 Local Header Offset 0008E200 (582144) │ │ │ │ -9F8F6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F8F6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F912 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F914 Length 0005 (5) │ │ │ │ -9F916 Flags 01 (1) 'Modification' │ │ │ │ -9F917 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F91B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F91D Length 000B (11) │ │ │ │ -9F91F Version 01 (1) │ │ │ │ -9F920 UID Size 04 (4) │ │ │ │ -9F921 UID 00000000 (0) │ │ │ │ -9F925 GID Size 04 (4) │ │ │ │ -9F926 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F92A CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -9F92E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F92F Created OS 03 (3) 'Unix' │ │ │ │ -9F930 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9F931 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F932 General Purpose Flag 0000 (0) │ │ │ │ -9F934 Compression Method 0000 (0) 'Stored' │ │ │ │ -9F936 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F93A CRC FC95F24B (4237685323) │ │ │ │ -9F93E Compressed Size 00001B84 (7044) │ │ │ │ -9F942 Uncompressed Size 00001B84 (7044) │ │ │ │ -9F946 Filename Length 0016 (22) │ │ │ │ -9F948 Extra Length 0018 (24) │ │ │ │ -9F94A Comment Length 0000 (0) │ │ │ │ -9F94C Disk Start 0000 (0) │ │ │ │ -9F94E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F950 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F954 Local Header Offset 0008F912 (588050) │ │ │ │ -9F958 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F958: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F96E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F970 Length 0005 (5) │ │ │ │ -9F972 Flags 01 (1) 'Modification' │ │ │ │ -9F973 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F977 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F979 Length 000B (11) │ │ │ │ -9F97B Version 01 (1) │ │ │ │ -9F97C UID Size 04 (4) │ │ │ │ -9F97D UID 00000000 (0) │ │ │ │ -9F981 GID Size 04 (4) │ │ │ │ -9F982 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F986 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -9F98A Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F98B Created OS 03 (3) 'Unix' │ │ │ │ -9F98C Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9F98D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F98E General Purpose Flag 0000 (0) │ │ │ │ -9F990 Compression Method 0000 (0) 'Stored' │ │ │ │ -9F992 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F996 CRC D0D71F86 (3503759238) │ │ │ │ -9F99A Compressed Size 00000B7B (2939) │ │ │ │ -9F99E Uncompressed Size 00000B7B (2939) │ │ │ │ -9F9A2 Filename Length 0016 (22) │ │ │ │ -9F9A4 Extra Length 0018 (24) │ │ │ │ -9F9A6 Comment Length 0000 (0) │ │ │ │ -9F9A8 Disk Start 0000 (0) │ │ │ │ -9F9AA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F9AC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F9B0 Local Header Offset 000914E6 (595174) │ │ │ │ -9F9B4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F9B4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F9CA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F9CC Length 0005 (5) │ │ │ │ -9F9CE Flags 01 (1) 'Modification' │ │ │ │ -9F9CF Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9F9D3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F9D5 Length 000B (11) │ │ │ │ -9F9D7 Version 01 (1) │ │ │ │ -9F9D8 UID Size 04 (4) │ │ │ │ -9F9D9 UID 00000000 (0) │ │ │ │ -9F9DD GID Size 04 (4) │ │ │ │ -9F9DE GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F9E2 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -9F9E6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F9E7 Created OS 03 (3) 'Unix' │ │ │ │ -9F9E8 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9F9E9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F9EA General Purpose Flag 0000 (0) │ │ │ │ -9F9EC Compression Method 0000 (0) 'Stored' │ │ │ │ -9F9EE Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9F9F2 CRC FFF9C4D2 (4294558930) │ │ │ │ -9F9F6 Compressed Size 0000138F (5007) │ │ │ │ -9F9FA Uncompressed Size 0000138F (5007) │ │ │ │ -9F9FE Filename Length 0016 (22) │ │ │ │ -9FA00 Extra Length 0018 (24) │ │ │ │ -9FA02 Comment Length 0000 (0) │ │ │ │ -9FA04 Disk Start 0000 (0) │ │ │ │ -9FA06 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FA08 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FA0C Local Header Offset 000920B1 (598193) │ │ │ │ -9FA10 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FA10: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FA26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FA28 Length 0005 (5) │ │ │ │ -9FA2A Flags 01 (1) 'Modification' │ │ │ │ -9FA2B Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FA2F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FA31 Length 000B (11) │ │ │ │ -9FA33 Version 01 (1) │ │ │ │ -9FA34 UID Size 04 (4) │ │ │ │ -9FA35 UID 00000000 (0) │ │ │ │ -9FA39 GID Size 04 (4) │ │ │ │ -9FA3A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FA3E CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -9FA42 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FA43 Created OS 03 (3) 'Unix' │ │ │ │ -9FA44 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9FA45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FA46 General Purpose Flag 0000 (0) │ │ │ │ -9FA48 Compression Method 0000 (0) 'Stored' │ │ │ │ -9FA4A Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FA4E CRC A1037E8E (2701360782) │ │ │ │ -9FA52 Compressed Size 0000145E (5214) │ │ │ │ -9FA56 Uncompressed Size 0000145E (5214) │ │ │ │ -9FA5A Filename Length 0016 (22) │ │ │ │ -9FA5C Extra Length 0018 (24) │ │ │ │ -9FA5E Comment Length 0000 (0) │ │ │ │ -9FA60 Disk Start 0000 (0) │ │ │ │ -9FA62 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FA64 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FA68 Local Header Offset 00093490 (603280) │ │ │ │ -9FA6C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FA6C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FA82 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FA84 Length 0005 (5) │ │ │ │ -9FA86 Flags 01 (1) 'Modification' │ │ │ │ -9FA87 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FA8B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FA8D Length 000B (11) │ │ │ │ -9FA8F Version 01 (1) │ │ │ │ -9FA90 UID Size 04 (4) │ │ │ │ -9FA91 UID 00000000 (0) │ │ │ │ -9FA95 GID Size 04 (4) │ │ │ │ -9FA96 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FA9A CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -9FA9E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FA9F Created OS 03 (3) 'Unix' │ │ │ │ -9FAA0 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9FAA1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FAA2 General Purpose Flag 0000 (0) │ │ │ │ -9FAA4 Compression Method 0000 (0) 'Stored' │ │ │ │ -9FAA6 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FAAA CRC 5E9E64F1 (1587438833) │ │ │ │ -9FAAE Compressed Size 000008EC (2284) │ │ │ │ -9FAB2 Uncompressed Size 000008EC (2284) │ │ │ │ -9FAB6 Filename Length 0016 (22) │ │ │ │ -9FAB8 Extra Length 0018 (24) │ │ │ │ -9FABA Comment Length 0000 (0) │ │ │ │ -9FABC Disk Start 0000 (0) │ │ │ │ -9FABE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FAC0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FAC4 Local Header Offset 0009493E (608574) │ │ │ │ -9FAC8 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FAC8: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FADE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FAE0 Length 0005 (5) │ │ │ │ -9FAE2 Flags 01 (1) 'Modification' │ │ │ │ -9FAE3 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FAE7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FAE9 Length 000B (11) │ │ │ │ -9FAEB Version 01 (1) │ │ │ │ -9FAEC UID Size 04 (4) │ │ │ │ -9FAED UID 00000000 (0) │ │ │ │ -9FAF1 GID Size 04 (4) │ │ │ │ -9FAF2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FAF6 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -9FAFA Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FAFB Created OS 03 (3) 'Unix' │ │ │ │ -9FAFC Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9FAFD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FAFE General Purpose Flag 0000 (0) │ │ │ │ -9FB00 Compression Method 0000 (0) 'Stored' │ │ │ │ -9FB02 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FB06 CRC 42E340AB (1122189483) │ │ │ │ -9FB0A Compressed Size 00001F2E (7982) │ │ │ │ -9FB0E Uncompressed Size 00001F2E (7982) │ │ │ │ -9FB12 Filename Length 001E (30) │ │ │ │ -9FB14 Extra Length 0018 (24) │ │ │ │ -9FB16 Comment Length 0000 (0) │ │ │ │ -9FB18 Disk Start 0000 (0) │ │ │ │ -9FB1A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FB1C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FB20 Local Header Offset 0009527A (610938) │ │ │ │ -9FB24 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FB24: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FB42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FB44 Length 0005 (5) │ │ │ │ -9FB46 Flags 01 (1) 'Modification' │ │ │ │ -9FB47 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FB4B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FB4D Length 000B (11) │ │ │ │ -9FB4F Version 01 (1) │ │ │ │ -9FB50 UID Size 04 (4) │ │ │ │ -9FB51 UID 00000000 (0) │ │ │ │ -9FB55 GID Size 04 (4) │ │ │ │ -9FB56 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FB5A CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -9FB5E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FB5F Created OS 03 (3) 'Unix' │ │ │ │ -9FB60 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FB61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FB62 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FB64 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FB66 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FB6A CRC 46D4ADFC (1188343292) │ │ │ │ -9FB6E Compressed Size 00003D71 (15729) │ │ │ │ -9FB72 Uncompressed Size 00016649 (91721) │ │ │ │ -9FB76 Filename Length 001A (26) │ │ │ │ -9FB78 Extra Length 0018 (24) │ │ │ │ -9FB7A Comment Length 0000 (0) │ │ │ │ -9FB7C Disk Start 0000 (0) │ │ │ │ -9FB7E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FB80 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FB84 Local Header Offset 00097200 (619008) │ │ │ │ -9FB88 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FB88: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FBA2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FBA4 Length 0005 (5) │ │ │ │ -9FBA6 Flags 01 (1) 'Modification' │ │ │ │ -9FBA7 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FBAB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FBAD Length 000B (11) │ │ │ │ -9FBAF Version 01 (1) │ │ │ │ -9FBB0 UID Size 04 (4) │ │ │ │ -9FBB1 UID 00000000 (0) │ │ │ │ -9FBB5 GID Size 04 (4) │ │ │ │ -9FBB6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FBBA CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -9FBBE Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FBBF Created OS 03 (3) 'Unix' │ │ │ │ -9FBC0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FBC1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FBC2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FBC4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FBC6 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FBCA CRC 228F80DD (579829981) │ │ │ │ -9FBCE Compressed Size 000029BF (10687) │ │ │ │ -9FBD2 Uncompressed Size 0000BA6A (47722) │ │ │ │ -9FBD6 Filename Length 0018 (24) │ │ │ │ -9FBD8 Extra Length 0018 (24) │ │ │ │ -9FBDA Comment Length 0000 (0) │ │ │ │ -9FBDC Disk Start 0000 (0) │ │ │ │ -9FBDE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FBE0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FBE4 Local Header Offset 0009AFC5 (634821) │ │ │ │ -9FBE8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FBE8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FC02 Length 0005 (5) │ │ │ │ -9FC04 Flags 01 (1) 'Modification' │ │ │ │ -9FC05 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FC09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FC0B Length 000B (11) │ │ │ │ -9FC0D Version 01 (1) │ │ │ │ -9FC0E UID Size 04 (4) │ │ │ │ -9FC0F UID 00000000 (0) │ │ │ │ -9FC13 GID Size 04 (4) │ │ │ │ -9FC14 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FC18 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -9FC1C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FC1D Created OS 03 (3) 'Unix' │ │ │ │ -9FC1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FC1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FC20 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FC22 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FC24 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FC28 CRC DCB3B516 (3702764822) │ │ │ │ -9FC2C Compressed Size 000000AE (174) │ │ │ │ -9FC30 Uncompressed Size 000000FC (252) │ │ │ │ -9FC34 Filename Length 0016 (22) │ │ │ │ -9FC36 Extra Length 0018 (24) │ │ │ │ -9FC38 Comment Length 0000 (0) │ │ │ │ -9FC3A Disk Start 0000 (0) │ │ │ │ -9FC3C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC3E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC42 Local Header Offset 0009D9D6 (645590) │ │ │ │ -9FC46 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FC46: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC5C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FC5E Length 0005 (5) │ │ │ │ -9FC60 Flags 01 (1) 'Modification' │ │ │ │ -9FC61 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FC65 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FC67 Length 000B (11) │ │ │ │ -9FC69 Version 01 (1) │ │ │ │ -9FC6A UID Size 04 (4) │ │ │ │ -9FC6B UID 00000000 (0) │ │ │ │ -9FC6F GID Size 04 (4) │ │ │ │ -9FC70 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FC74 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -9FC78 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FC79 Created OS 03 (3) 'Unix' │ │ │ │ -9FC7A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FC7B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FC7C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FC7E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FC80 Modification Time 5B5F6A46 (1532979782) 'Fri Oct 31 13:18:12 2025' │ │ │ │ -9FC84 CRC 58439733 (1480824627) │ │ │ │ -9FC88 Compressed Size 00000077 (119) │ │ │ │ -9FC8C Uncompressed Size 000000A2 (162) │ │ │ │ -9FC90 Filename Length 002D (45) │ │ │ │ -9FC92 Extra Length 0018 (24) │ │ │ │ -9FC94 Comment Length 0000 (0) │ │ │ │ -9FC96 Disk Start 0000 (0) │ │ │ │ -9FC98 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC9A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC9E Local Header Offset 0009DAD4 (645844) │ │ │ │ -9FCA2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FCA2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FCCF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FCD1 Length 0005 (5) │ │ │ │ -9FCD3 Flags 01 (1) 'Modification' │ │ │ │ -9FCD4 Modification Time 6904B715 (1761916693) 'Fri Oct 31 13:18:13 2025' │ │ │ │ -9FCD8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FCDA Length 000B (11) │ │ │ │ -9FCDC Version 01 (1) │ │ │ │ -9FCDD UID Size 04 (4) │ │ │ │ -9FCDE UID 00000000 (0) │ │ │ │ -9FCE2 GID Size 04 (4) │ │ │ │ -9FCE3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FCE7 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -9FCEB Number of this disk 0000 (0) │ │ │ │ -9FCED Central Dir Disk no 0000 (0) │ │ │ │ -9FCEF Entries in this disk 005B (91) │ │ │ │ -9FCF1 Total Entries 005B (91) │ │ │ │ -9FCF3 Size of Central Dir 00002135 (8501) │ │ │ │ -9FCF7 Offset to Central Dir 0009DBB2 (646066) │ │ │ │ -9FCFB Comment Length 0000 (0) │ │ │ │ +9DAE0 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +9DAE4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DAE5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DAE6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DAE8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DAEA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DAEE CRC 58439733 (1480824627) │ │ │ │ +9DAF2 Compressed Size 00000077 (119) │ │ │ │ +9DAF6 Uncompressed Size 000000A2 (162) │ │ │ │ +9DAFA Filename Length 002D (45) │ │ │ │ +9DAFC Extra Length 001C (28) │ │ │ │ +9DAFE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DAFE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DB2B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DB2D Length 0009 (9) │ │ │ │ +9DB2F Flags 03 (3) 'Modification Access' │ │ │ │ +9DB30 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DB34 Access Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DB38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DB3A Length 000B (11) │ │ │ │ +9DB3C Version 01 (1) │ │ │ │ +9DB3D UID Size 04 (4) │ │ │ │ +9DB3E UID 00000000 (0) │ │ │ │ +9DB42 GID Size 04 (4) │ │ │ │ +9DB43 GID 00000000 (0) │ │ │ │ +9DB47 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +9DBBE CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +9DBC2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DBC3 Created OS 03 (3) 'Unix' │ │ │ │ +9DBC4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9DBC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DBC6 General Purpose Flag 0000 (0) │ │ │ │ +9DBC8 Compression Method 0000 (0) 'Stored' │ │ │ │ +9DBCA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DBCE CRC 2CAB616F (749429103) │ │ │ │ +9DBD2 Compressed Size 00000014 (20) │ │ │ │ +9DBD6 Uncompressed Size 00000014 (20) │ │ │ │ +9DBDA Filename Length 0008 (8) │ │ │ │ +9DBDC Extra Length 0018 (24) │ │ │ │ +9DBDE Comment Length 0000 (0) │ │ │ │ +9DBE0 Disk Start 0000 (0) │ │ │ │ +9DBE2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DBE4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DBE8 Local Header Offset 00000000 (0) │ │ │ │ +9DBEC Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DBEC: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DBF4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DBF6 Length 0005 (5) │ │ │ │ +9DBF8 Flags 01 (1) 'Modification' │ │ │ │ +9DBF9 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DBFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DBFF Length 000B (11) │ │ │ │ +9DC01 Version 01 (1) │ │ │ │ +9DC02 UID Size 04 (4) │ │ │ │ +9DC03 UID 00000000 (0) │ │ │ │ +9DC07 GID Size 04 (4) │ │ │ │ +9DC08 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DC0C CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +9DC10 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DC11 Created OS 03 (3) 'Unix' │ │ │ │ +9DC12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DC13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DC14 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DC16 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DC18 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DC1C CRC 203D4469 (540886121) │ │ │ │ +9DC20 Compressed Size 000015AD (5549) │ │ │ │ +9DC24 Uncompressed Size 00004602 (17922) │ │ │ │ +9DC28 Filename Length 0014 (20) │ │ │ │ +9DC2A Extra Length 0018 (24) │ │ │ │ +9DC2C Comment Length 0000 (0) │ │ │ │ +9DC2E Disk Start 0000 (0) │ │ │ │ +9DC30 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DC32 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DC36 Local Header Offset 00000056 (86) │ │ │ │ +9DC3A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DC3A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DC4E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DC50 Length 0005 (5) │ │ │ │ +9DC52 Flags 01 (1) 'Modification' │ │ │ │ +9DC53 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DC57 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DC59 Length 000B (11) │ │ │ │ +9DC5B Version 01 (1) │ │ │ │ +9DC5C UID Size 04 (4) │ │ │ │ +9DC5D UID 00000000 (0) │ │ │ │ +9DC61 GID Size 04 (4) │ │ │ │ +9DC62 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DC66 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +9DC6A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DC6B Created OS 03 (3) 'Unix' │ │ │ │ +9DC6C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DC6D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DC6E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DC70 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DC72 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DC76 CRC 1EC1D7C5 (516020165) │ │ │ │ +9DC7A Compressed Size 000006D5 (1749) │ │ │ │ +9DC7E Uncompressed Size 00001241 (4673) │ │ │ │ +9DC82 Filename Length 0013 (19) │ │ │ │ +9DC84 Extra Length 0018 (24) │ │ │ │ +9DC86 Comment Length 0000 (0) │ │ │ │ +9DC88 Disk Start 0000 (0) │ │ │ │ +9DC8A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DC8C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DC90 Local Header Offset 00001651 (5713) │ │ │ │ +9DC94 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DC94: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DCA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DCA9 Length 0005 (5) │ │ │ │ +9DCAB Flags 01 (1) 'Modification' │ │ │ │ +9DCAC Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DCB0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DCB2 Length 000B (11) │ │ │ │ +9DCB4 Version 01 (1) │ │ │ │ +9DCB5 UID Size 04 (4) │ │ │ │ +9DCB6 UID 00000000 (0) │ │ │ │ +9DCBA GID Size 04 (4) │ │ │ │ +9DCBB GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DCBF CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +9DCC3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DCC4 Created OS 03 (3) 'Unix' │ │ │ │ +9DCC5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DCC6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DCC7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DCC9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DCCB Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DCCF CRC 0E61F7BC (241301436) │ │ │ │ +9DCD3 Compressed Size 00002D9F (11679) │ │ │ │ +9DCD7 Uncompressed Size 0000D0BF (53439) │ │ │ │ +9DCDB Filename Length 0014 (20) │ │ │ │ +9DCDD Extra Length 0018 (24) │ │ │ │ +9DCDF Comment Length 0000 (0) │ │ │ │ +9DCE1 Disk Start 0000 (0) │ │ │ │ +9DCE3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DCE5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DCE9 Local Header Offset 00001D73 (7539) │ │ │ │ +9DCED Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DCED: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DD01 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DD03 Length 0005 (5) │ │ │ │ +9DD05 Flags 01 (1) 'Modification' │ │ │ │ +9DD06 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DD0A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DD0C Length 000B (11) │ │ │ │ +9DD0E Version 01 (1) │ │ │ │ +9DD0F UID Size 04 (4) │ │ │ │ +9DD10 UID 00000000 (0) │ │ │ │ +9DD14 GID Size 04 (4) │ │ │ │ +9DD15 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DD19 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +9DD1D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DD1E Created OS 03 (3) 'Unix' │ │ │ │ +9DD1F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DD20 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DD21 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DD23 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DD25 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DD29 CRC B8F4A452 (3103040594) │ │ │ │ +9DD2D Compressed Size 000003F0 (1008) │ │ │ │ +9DD31 Uncompressed Size 00000876 (2166) │ │ │ │ +9DD35 Filename Length 0014 (20) │ │ │ │ +9DD37 Extra Length 0018 (24) │ │ │ │ +9DD39 Comment Length 0000 (0) │ │ │ │ +9DD3B Disk Start 0000 (0) │ │ │ │ +9DD3D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DD3F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DD43 Local Header Offset 00004B60 (19296) │ │ │ │ +9DD47 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DD47: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DD5B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DD5D Length 0005 (5) │ │ │ │ +9DD5F Flags 01 (1) 'Modification' │ │ │ │ +9DD60 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DD64 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DD66 Length 000B (11) │ │ │ │ +9DD68 Version 01 (1) │ │ │ │ +9DD69 UID Size 04 (4) │ │ │ │ +9DD6A UID 00000000 (0) │ │ │ │ +9DD6E GID Size 04 (4) │ │ │ │ +9DD6F GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DD73 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +9DD77 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DD78 Created OS 03 (3) 'Unix' │ │ │ │ +9DD79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DD7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DD7B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DD7D Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DD7F Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DD83 CRC D32DA388 (3542983560) │ │ │ │ +9DD87 Compressed Size 000001AE (430) │ │ │ │ +9DD8B Uncompressed Size 000002FC (764) │ │ │ │ +9DD8F Filename Length 0011 (17) │ │ │ │ +9DD91 Extra Length 0018 (24) │ │ │ │ +9DD93 Comment Length 0000 (0) │ │ │ │ +9DD95 Disk Start 0000 (0) │ │ │ │ +9DD97 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DD99 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DD9D Local Header Offset 00004F9E (20382) │ │ │ │ +9DDA1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DDA1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DDB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DDB4 Length 0005 (5) │ │ │ │ +9DDB6 Flags 01 (1) 'Modification' │ │ │ │ +9DDB7 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DDBB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DDBD Length 000B (11) │ │ │ │ +9DDBF Version 01 (1) │ │ │ │ +9DDC0 UID Size 04 (4) │ │ │ │ +9DDC1 UID 00000000 (0) │ │ │ │ +9DDC5 GID Size 04 (4) │ │ │ │ +9DDC6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DDCA CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +9DDCE Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DDCF Created OS 03 (3) 'Unix' │ │ │ │ +9DDD0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DDD1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DDD2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DDD4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DDD6 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DDDA CRC 216D3405 (560804869) │ │ │ │ +9DDDE Compressed Size 000020C6 (8390) │ │ │ │ +9DDE2 Uncompressed Size 0000B4B0 (46256) │ │ │ │ +9DDE6 Filename Length 001B (27) │ │ │ │ +9DDE8 Extra Length 0018 (24) │ │ │ │ +9DDEA Comment Length 0000 (0) │ │ │ │ +9DDEC Disk Start 0000 (0) │ │ │ │ +9DDEE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DDF0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DDF4 Local Header Offset 00005197 (20887) │ │ │ │ +9DDF8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DDF8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DE13 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DE15 Length 0005 (5) │ │ │ │ +9DE17 Flags 01 (1) 'Modification' │ │ │ │ +9DE18 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DE1C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DE1E Length 000B (11) │ │ │ │ +9DE20 Version 01 (1) │ │ │ │ +9DE21 UID Size 04 (4) │ │ │ │ +9DE22 UID 00000000 (0) │ │ │ │ +9DE26 GID Size 04 (4) │ │ │ │ +9DE27 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DE2B CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +9DE2F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DE30 Created OS 03 (3) 'Unix' │ │ │ │ +9DE31 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DE32 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DE33 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DE35 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DE37 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DE3B CRC CB35CCED (3409300717) │ │ │ │ +9DE3F Compressed Size 00000E6F (3695) │ │ │ │ +9DE43 Uncompressed Size 000030B2 (12466) │ │ │ │ +9DE47 Filename Length 001D (29) │ │ │ │ +9DE49 Extra Length 0018 (24) │ │ │ │ +9DE4B Comment Length 0000 (0) │ │ │ │ +9DE4D Disk Start 0000 (0) │ │ │ │ +9DE4F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DE51 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DE55 Local Header Offset 000072B2 (29362) │ │ │ │ +9DE59 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DE59: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DE76 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DE78 Length 0005 (5) │ │ │ │ +9DE7A Flags 01 (1) 'Modification' │ │ │ │ +9DE7B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DE7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DE81 Length 000B (11) │ │ │ │ +9DE83 Version 01 (1) │ │ │ │ +9DE84 UID Size 04 (4) │ │ │ │ +9DE85 UID 00000000 (0) │ │ │ │ +9DE89 GID Size 04 (4) │ │ │ │ +9DE8A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DE8E CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +9DE92 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DE93 Created OS 03 (3) 'Unix' │ │ │ │ +9DE94 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DE95 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DE96 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DE98 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DE9A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DE9E CRC D01F57B3 (3491714995) │ │ │ │ +9DEA2 Compressed Size 00000972 (2418) │ │ │ │ +9DEA6 Uncompressed Size 00001CB2 (7346) │ │ │ │ +9DEAA Filename Length 0019 (25) │ │ │ │ +9DEAC Extra Length 0018 (24) │ │ │ │ +9DEAE Comment Length 0000 (0) │ │ │ │ +9DEB0 Disk Start 0000 (0) │ │ │ │ +9DEB2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DEB4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DEB8 Local Header Offset 00008178 (33144) │ │ │ │ +9DEBC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DEBC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DED5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DED7 Length 0005 (5) │ │ │ │ +9DED9 Flags 01 (1) 'Modification' │ │ │ │ +9DEDA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DEDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DEE0 Length 000B (11) │ │ │ │ +9DEE2 Version 01 (1) │ │ │ │ +9DEE3 UID Size 04 (4) │ │ │ │ +9DEE4 UID 00000000 (0) │ │ │ │ +9DEE8 GID Size 04 (4) │ │ │ │ +9DEE9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DEED CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +9DEF1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DEF2 Created OS 03 (3) 'Unix' │ │ │ │ +9DEF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DEF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DEF5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DEF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DEF9 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DEFD CRC 274AFEE1 (659226337) │ │ │ │ +9DF01 Compressed Size 0000387E (14462) │ │ │ │ +9DF05 Uncompressed Size 0000F7F4 (63476) │ │ │ │ +9DF09 Filename Length 0015 (21) │ │ │ │ +9DF0B Extra Length 0018 (24) │ │ │ │ +9DF0D Comment Length 0000 (0) │ │ │ │ +9DF0F Disk Start 0000 (0) │ │ │ │ +9DF11 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DF13 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DF17 Local Header Offset 00008B3D (35645) │ │ │ │ +9DF1B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DF1B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DF30 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DF32 Length 0005 (5) │ │ │ │ +9DF34 Flags 01 (1) 'Modification' │ │ │ │ +9DF35 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DF39 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DF3B Length 000B (11) │ │ │ │ +9DF3D Version 01 (1) │ │ │ │ +9DF3E UID Size 04 (4) │ │ │ │ +9DF3F UID 00000000 (0) │ │ │ │ +9DF43 GID Size 04 (4) │ │ │ │ +9DF44 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DF48 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +9DF4C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DF4D Created OS 03 (3) 'Unix' │ │ │ │ +9DF4E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DF4F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DF50 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DF52 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DF54 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DF58 CRC 45EF58BC (1173313724) │ │ │ │ +9DF5C Compressed Size 0000AADC (43740) │ │ │ │ +9DF60 Uncompressed Size 0003DFDE (253918) │ │ │ │ +9DF64 Filename Length 0012 (18) │ │ │ │ +9DF66 Extra Length 0018 (24) │ │ │ │ +9DF68 Comment Length 0000 (0) │ │ │ │ +9DF6A Disk Start 0000 (0) │ │ │ │ +9DF6C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DF6E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DF72 Local Header Offset 0000C40A (50186) │ │ │ │ +9DF76 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DF76: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DF88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DF8A Length 0005 (5) │ │ │ │ +9DF8C Flags 01 (1) 'Modification' │ │ │ │ +9DF8D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DF91 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DF93 Length 000B (11) │ │ │ │ +9DF95 Version 01 (1) │ │ │ │ +9DF96 UID Size 04 (4) │ │ │ │ +9DF97 UID 00000000 (0) │ │ │ │ +9DF9B GID Size 04 (4) │ │ │ │ +9DF9C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DFA0 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +9DFA4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9DFA5 Created OS 03 (3) 'Unix' │ │ │ │ +9DFA6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9DFA7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9DFA8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9DFAA Compression Method 0008 (8) 'Deflated' │ │ │ │ +9DFAC Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9DFB0 CRC 3A18F4DB (974714075) │ │ │ │ +9DFB4 Compressed Size 00003B20 (15136) │ │ │ │ +9DFB8 Uncompressed Size 0001B2A0 (111264) │ │ │ │ +9DFBC Filename Length 0015 (21) │ │ │ │ +9DFBE Extra Length 0018 (24) │ │ │ │ +9DFC0 Comment Length 0000 (0) │ │ │ │ +9DFC2 Disk Start 0000 (0) │ │ │ │ +9DFC4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9DFC6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9DFCA Local Header Offset 00016F32 (94002) │ │ │ │ +9DFCE Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9DFCE: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9DFE3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9DFE5 Length 0005 (5) │ │ │ │ +9DFE7 Flags 01 (1) 'Modification' │ │ │ │ +9DFE8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9DFEC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9DFEE Length 000B (11) │ │ │ │ +9DFF0 Version 01 (1) │ │ │ │ +9DFF1 UID Size 04 (4) │ │ │ │ +9DFF2 UID 00000000 (0) │ │ │ │ +9DFF6 GID Size 04 (4) │ │ │ │ +9DFF7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9DFFB CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +9DFFF Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E000 Created OS 03 (3) 'Unix' │ │ │ │ +9E001 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E002 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E003 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E005 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E007 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E00B CRC 76B59E6D (1991614061) │ │ │ │ +9E00F Compressed Size 00009087 (36999) │ │ │ │ +9E013 Uncompressed Size 0003D05F (249951) │ │ │ │ +9E017 Filename Length 0014 (20) │ │ │ │ +9E019 Extra Length 0018 (24) │ │ │ │ +9E01B Comment Length 0000 (0) │ │ │ │ +9E01D Disk Start 0000 (0) │ │ │ │ +9E01F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E021 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E025 Local Header Offset 0001AAA1 (109217) │ │ │ │ +9E029 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E029: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E03D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E03F Length 0005 (5) │ │ │ │ +9E041 Flags 01 (1) 'Modification' │ │ │ │ +9E042 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E046 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E048 Length 000B (11) │ │ │ │ +9E04A Version 01 (1) │ │ │ │ +9E04B UID Size 04 (4) │ │ │ │ +9E04C UID 00000000 (0) │ │ │ │ +9E050 GID Size 04 (4) │ │ │ │ +9E051 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E055 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +9E059 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E05A Created OS 03 (3) 'Unix' │ │ │ │ +9E05B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E05C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E05D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E05F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E061 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E065 CRC E1D62D32 (3788909874) │ │ │ │ +9E069 Compressed Size 00002A68 (10856) │ │ │ │ +9E06D Uncompressed Size 0001151F (70943) │ │ │ │ +9E071 Filename Length 0016 (22) │ │ │ │ +9E073 Extra Length 0018 (24) │ │ │ │ +9E075 Comment Length 0000 (0) │ │ │ │ +9E077 Disk Start 0000 (0) │ │ │ │ +9E079 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E07B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E07F Local Header Offset 00023B76 (146294) │ │ │ │ +9E083 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E083: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E099 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E09B Length 0005 (5) │ │ │ │ +9E09D Flags 01 (1) 'Modification' │ │ │ │ +9E09E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E0A2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E0A4 Length 000B (11) │ │ │ │ +9E0A6 Version 01 (1) │ │ │ │ +9E0A7 UID Size 04 (4) │ │ │ │ +9E0A8 UID 00000000 (0) │ │ │ │ +9E0AC GID Size 04 (4) │ │ │ │ +9E0AD GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E0B1 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +9E0B5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E0B6 Created OS 03 (3) 'Unix' │ │ │ │ +9E0B7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E0B8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E0B9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E0BB Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E0BD Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E0C1 CRC 99693BA1 (2573810593) │ │ │ │ +9E0C5 Compressed Size 000014D8 (5336) │ │ │ │ +9E0C9 Uncompressed Size 00005176 (20854) │ │ │ │ +9E0CD Filename Length 001D (29) │ │ │ │ +9E0CF Extra Length 0018 (24) │ │ │ │ +9E0D1 Comment Length 0000 (0) │ │ │ │ +9E0D3 Disk Start 0000 (0) │ │ │ │ +9E0D5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E0D7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E0DB Local Header Offset 0002662E (157230) │ │ │ │ +9E0DF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E0DF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E0FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E0FE Length 0005 (5) │ │ │ │ +9E100 Flags 01 (1) 'Modification' │ │ │ │ +9E101 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E105 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E107 Length 000B (11) │ │ │ │ +9E109 Version 01 (1) │ │ │ │ +9E10A UID Size 04 (4) │ │ │ │ +9E10B UID 00000000 (0) │ │ │ │ +9E10F GID Size 04 (4) │ │ │ │ +9E110 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E114 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +9E118 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E119 Created OS 03 (3) 'Unix' │ │ │ │ +9E11A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E11B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E11C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E11E Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E120 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E124 CRC F7498B6D (4148792173) │ │ │ │ +9E128 Compressed Size 000037F9 (14329) │ │ │ │ +9E12C Uncompressed Size 0000E9F0 (59888) │ │ │ │ +9E130 Filename Length 001C (28) │ │ │ │ +9E132 Extra Length 0018 (24) │ │ │ │ +9E134 Comment Length 0000 (0) │ │ │ │ +9E136 Disk Start 0000 (0) │ │ │ │ +9E138 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E13A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E13E Local Header Offset 00027B5D (162653) │ │ │ │ +9E142 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E142: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E15E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E160 Length 0005 (5) │ │ │ │ +9E162 Flags 01 (1) 'Modification' │ │ │ │ +9E163 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E167 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E169 Length 000B (11) │ │ │ │ +9E16B Version 01 (1) │ │ │ │ +9E16C UID Size 04 (4) │ │ │ │ +9E16D UID 00000000 (0) │ │ │ │ +9E171 GID Size 04 (4) │ │ │ │ +9E172 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E176 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +9E17A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E17B Created OS 03 (3) 'Unix' │ │ │ │ +9E17C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E17D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E17E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E180 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E182 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E186 CRC 6A63C5E7 (1784923623) │ │ │ │ +9E18A Compressed Size 000006A0 (1696) │ │ │ │ +9E18E Uncompressed Size 000011F4 (4596) │ │ │ │ +9E192 Filename Length 001C (28) │ │ │ │ +9E194 Extra Length 0018 (24) │ │ │ │ +9E196 Comment Length 0000 (0) │ │ │ │ +9E198 Disk Start 0000 (0) │ │ │ │ +9E19A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E19C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E1A0 Local Header Offset 0002B3AC (177068) │ │ │ │ +9E1A4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E1A4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E1C0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E1C2 Length 0005 (5) │ │ │ │ +9E1C4 Flags 01 (1) 'Modification' │ │ │ │ +9E1C5 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E1C9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E1CB Length 000B (11) │ │ │ │ +9E1CD Version 01 (1) │ │ │ │ +9E1CE UID Size 04 (4) │ │ │ │ +9E1CF UID 00000000 (0) │ │ │ │ +9E1D3 GID Size 04 (4) │ │ │ │ +9E1D4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E1D8 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +9E1DC Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E1DD Created OS 03 (3) 'Unix' │ │ │ │ +9E1DE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E1DF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E1E0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E1E2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E1E4 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E1E8 CRC A55B953B (2774242619) │ │ │ │ +9E1EC Compressed Size 0000107D (4221) │ │ │ │ +9E1F0 Uncompressed Size 00004BFF (19455) │ │ │ │ +9E1F4 Filename Length 001B (27) │ │ │ │ +9E1F6 Extra Length 0018 (24) │ │ │ │ +9E1F8 Comment Length 0000 (0) │ │ │ │ +9E1FA Disk Start 0000 (0) │ │ │ │ +9E1FC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E1FE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E202 Local Header Offset 0002BAA2 (178850) │ │ │ │ +9E206 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E206: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E221 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E223 Length 0005 (5) │ │ │ │ +9E225 Flags 01 (1) 'Modification' │ │ │ │ +9E226 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E22A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E22C Length 000B (11) │ │ │ │ +9E22E Version 01 (1) │ │ │ │ +9E22F UID Size 04 (4) │ │ │ │ +9E230 UID 00000000 (0) │ │ │ │ +9E234 GID Size 04 (4) │ │ │ │ +9E235 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E239 CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +9E23D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E23E Created OS 03 (3) 'Unix' │ │ │ │ +9E23F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E240 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E241 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E243 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E245 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E249 CRC 424992B3 (1112117939) │ │ │ │ +9E24D Compressed Size 000033AA (13226) │ │ │ │ +9E251 Uncompressed Size 0000BC94 (48276) │ │ │ │ +9E255 Filename Length 001D (29) │ │ │ │ +9E257 Extra Length 0018 (24) │ │ │ │ +9E259 Comment Length 0000 (0) │ │ │ │ +9E25B Disk Start 0000 (0) │ │ │ │ +9E25D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E25F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E263 Local Header Offset 0002CB74 (183156) │ │ │ │ +9E267 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E267: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E284 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E286 Length 0005 (5) │ │ │ │ +9E288 Flags 01 (1) 'Modification' │ │ │ │ +9E289 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E28D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E28F Length 000B (11) │ │ │ │ +9E291 Version 01 (1) │ │ │ │ +9E292 UID Size 04 (4) │ │ │ │ +9E293 UID 00000000 (0) │ │ │ │ +9E297 GID Size 04 (4) │ │ │ │ +9E298 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E29C CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +9E2A0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E2A1 Created OS 03 (3) 'Unix' │ │ │ │ +9E2A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E2A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E2A4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E2A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E2A8 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E2AC CRC B5695653 (3043579475) │ │ │ │ +9E2B0 Compressed Size 00000D6B (3435) │ │ │ │ +9E2B4 Uncompressed Size 00003876 (14454) │ │ │ │ +9E2B8 Filename Length 001D (29) │ │ │ │ +9E2BA Extra Length 0018 (24) │ │ │ │ +9E2BC Comment Length 0000 (0) │ │ │ │ +9E2BE Disk Start 0000 (0) │ │ │ │ +9E2C0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E2C2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E2C6 Local Header Offset 0002FF75 (196469) │ │ │ │ +9E2CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E2CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E2E7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E2E9 Length 0005 (5) │ │ │ │ +9E2EB Flags 01 (1) 'Modification' │ │ │ │ +9E2EC Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E2F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E2F2 Length 000B (11) │ │ │ │ +9E2F4 Version 01 (1) │ │ │ │ +9E2F5 UID Size 04 (4) │ │ │ │ +9E2F6 UID 00000000 (0) │ │ │ │ +9E2FA GID Size 04 (4) │ │ │ │ +9E2FB GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E2FF CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +9E303 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E304 Created OS 03 (3) 'Unix' │ │ │ │ +9E305 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E306 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E307 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E309 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E30B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E30F CRC 07D12257 (131146327) │ │ │ │ +9E313 Compressed Size 00001C6C (7276) │ │ │ │ +9E317 Uncompressed Size 0000C186 (49542) │ │ │ │ +9E31B Filename Length 001A (26) │ │ │ │ +9E31D Extra Length 0018 (24) │ │ │ │ +9E31F Comment Length 0000 (0) │ │ │ │ +9E321 Disk Start 0000 (0) │ │ │ │ +9E323 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E325 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E329 Local Header Offset 00030D37 (199991) │ │ │ │ +9E32D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E32D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E347 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E349 Length 0005 (5) │ │ │ │ +9E34B Flags 01 (1) 'Modification' │ │ │ │ +9E34C Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E350 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E352 Length 000B (11) │ │ │ │ +9E354 Version 01 (1) │ │ │ │ +9E355 UID Size 04 (4) │ │ │ │ +9E356 UID 00000000 (0) │ │ │ │ +9E35A GID Size 04 (4) │ │ │ │ +9E35B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E35F CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +9E363 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E364 Created OS 03 (3) 'Unix' │ │ │ │ +9E365 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E366 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E367 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E369 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E36B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E36F CRC 65574916 (1700219158) │ │ │ │ +9E373 Compressed Size 000003A3 (931) │ │ │ │ +9E377 Uncompressed Size 0000088E (2190) │ │ │ │ +9E37B Filename Length 0012 (18) │ │ │ │ +9E37D Extra Length 0018 (24) │ │ │ │ +9E37F Comment Length 0000 (0) │ │ │ │ +9E381 Disk Start 0000 (0) │ │ │ │ +9E383 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E385 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E389 Local Header Offset 000329F7 (207351) │ │ │ │ +9E38D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E38D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E39F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E3A1 Length 0005 (5) │ │ │ │ +9E3A3 Flags 01 (1) 'Modification' │ │ │ │ +9E3A4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E3A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E3AA Length 000B (11) │ │ │ │ +9E3AC Version 01 (1) │ │ │ │ +9E3AD UID Size 04 (4) │ │ │ │ +9E3AE UID 00000000 (0) │ │ │ │ +9E3B2 GID Size 04 (4) │ │ │ │ +9E3B3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E3B7 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +9E3BB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E3BC Created OS 03 (3) 'Unix' │ │ │ │ +9E3BD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E3BE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E3BF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E3C1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E3C3 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E3C7 CRC 83472134 (2202476852) │ │ │ │ +9E3CB Compressed Size 000001D4 (468) │ │ │ │ +9E3CF Uncompressed Size 00000311 (785) │ │ │ │ +9E3D3 Filename Length 0020 (32) │ │ │ │ +9E3D5 Extra Length 0018 (24) │ │ │ │ +9E3D7 Comment Length 0000 (0) │ │ │ │ +9E3D9 Disk Start 0000 (0) │ │ │ │ +9E3DB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E3DD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E3E1 Local Header Offset 00032DE6 (208358) │ │ │ │ +9E3E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E3E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E405 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E407 Length 0005 (5) │ │ │ │ +9E409 Flags 01 (1) 'Modification' │ │ │ │ +9E40A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E40E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E410 Length 000B (11) │ │ │ │ +9E412 Version 01 (1) │ │ │ │ +9E413 UID Size 04 (4) │ │ │ │ +9E414 UID 00000000 (0) │ │ │ │ +9E418 GID Size 04 (4) │ │ │ │ +9E419 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E41D CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +9E421 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E422 Created OS 03 (3) 'Unix' │ │ │ │ +9E423 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E424 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E425 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E427 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E429 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E42D CRC 5EB37717 (1588819735) │ │ │ │ +9E431 Compressed Size 000017AA (6058) │ │ │ │ +9E435 Uncompressed Size 00009CD3 (40147) │ │ │ │ +9E439 Filename Length 001B (27) │ │ │ │ +9E43B Extra Length 0018 (24) │ │ │ │ +9E43D Comment Length 0000 (0) │ │ │ │ +9E43F Disk Start 0000 (0) │ │ │ │ +9E441 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E443 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E447 Local Header Offset 00033014 (208916) │ │ │ │ +9E44B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E44B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E466 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E468 Length 0005 (5) │ │ │ │ +9E46A Flags 01 (1) 'Modification' │ │ │ │ +9E46B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E46F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E471 Length 000B (11) │ │ │ │ +9E473 Version 01 (1) │ │ │ │ +9E474 UID Size 04 (4) │ │ │ │ +9E475 UID 00000000 (0) │ │ │ │ +9E479 GID Size 04 (4) │ │ │ │ +9E47A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E47E CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +9E482 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E483 Created OS 03 (3) 'Unix' │ │ │ │ +9E484 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E485 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E486 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E488 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E48A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E48E CRC 5F73B5AE (1601418670) │ │ │ │ +9E492 Compressed Size 00001371 (4977) │ │ │ │ +9E496 Uncompressed Size 00003B66 (15206) │ │ │ │ +9E49A Filename Length 0015 (21) │ │ │ │ +9E49C Extra Length 0018 (24) │ │ │ │ +9E49E Comment Length 0000 (0) │ │ │ │ +9E4A0 Disk Start 0000 (0) │ │ │ │ +9E4A2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E4A4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E4A8 Local Header Offset 00034813 (215059) │ │ │ │ +9E4AC Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E4AC: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E4C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E4C3 Length 0005 (5) │ │ │ │ +9E4C5 Flags 01 (1) 'Modification' │ │ │ │ +9E4C6 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E4CA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E4CC Length 000B (11) │ │ │ │ +9E4CE Version 01 (1) │ │ │ │ +9E4CF UID Size 04 (4) │ │ │ │ +9E4D0 UID 00000000 (0) │ │ │ │ +9E4D4 GID Size 04 (4) │ │ │ │ +9E4D5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E4D9 CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +9E4DD Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E4DE Created OS 03 (3) 'Unix' │ │ │ │ +9E4DF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E4E0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E4E1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E4E3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E4E5 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E4E9 CRC 7FAE09B6 (2142112182) │ │ │ │ +9E4ED Compressed Size 00000AD1 (2769) │ │ │ │ +9E4F1 Uncompressed Size 00002135 (8501) │ │ │ │ +9E4F5 Filename Length 0011 (17) │ │ │ │ +9E4F7 Extra Length 0018 (24) │ │ │ │ +9E4F9 Comment Length 0000 (0) │ │ │ │ +9E4FB Disk Start 0000 (0) │ │ │ │ +9E4FD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E4FF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E503 Local Header Offset 00035BD3 (220115) │ │ │ │ +9E507 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E507: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E518 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E51A Length 0005 (5) │ │ │ │ +9E51C Flags 01 (1) 'Modification' │ │ │ │ +9E51D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E521 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E523 Length 000B (11) │ │ │ │ +9E525 Version 01 (1) │ │ │ │ +9E526 UID Size 04 (4) │ │ │ │ +9E527 UID 00000000 (0) │ │ │ │ +9E52B GID Size 04 (4) │ │ │ │ +9E52C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E530 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +9E534 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E535 Created OS 03 (3) 'Unix' │ │ │ │ +9E536 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E537 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E538 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E53A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E53C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E540 CRC 23C85C8D (600333453) │ │ │ │ +9E544 Compressed Size 000003FE (1022) │ │ │ │ +9E548 Uncompressed Size 00000E99 (3737) │ │ │ │ +9E54C Filename Length 0014 (20) │ │ │ │ +9E54E Extra Length 0018 (24) │ │ │ │ +9E550 Comment Length 0000 (0) │ │ │ │ +9E552 Disk Start 0000 (0) │ │ │ │ +9E554 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E556 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E55A Local Header Offset 000366EF (222959) │ │ │ │ +9E55E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E55E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E572 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E574 Length 0005 (5) │ │ │ │ +9E576 Flags 01 (1) 'Modification' │ │ │ │ +9E577 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E57B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E57D Length 000B (11) │ │ │ │ +9E57F Version 01 (1) │ │ │ │ +9E580 UID Size 04 (4) │ │ │ │ +9E581 UID 00000000 (0) │ │ │ │ +9E585 GID Size 04 (4) │ │ │ │ +9E586 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E58A CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +9E58E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E58F Created OS 03 (3) 'Unix' │ │ │ │ +9E590 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E591 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E592 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E594 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E596 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E59A CRC EB83ACB7 (3951275191) │ │ │ │ +9E59E Compressed Size 00001261 (4705) │ │ │ │ +9E5A2 Uncompressed Size 00003469 (13417) │ │ │ │ +9E5A6 Filename Length 0014 (20) │ │ │ │ +9E5A8 Extra Length 0018 (24) │ │ │ │ +9E5AA Comment Length 0000 (0) │ │ │ │ +9E5AC Disk Start 0000 (0) │ │ │ │ +9E5AE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E5B0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E5B4 Local Header Offset 00036B3B (224059) │ │ │ │ +9E5B8 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E5B8: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E5CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E5CE Length 0005 (5) │ │ │ │ +9E5D0 Flags 01 (1) 'Modification' │ │ │ │ +9E5D1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E5D5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E5D7 Length 000B (11) │ │ │ │ +9E5D9 Version 01 (1) │ │ │ │ +9E5DA UID Size 04 (4) │ │ │ │ +9E5DB UID 00000000 (0) │ │ │ │ +9E5DF GID Size 04 (4) │ │ │ │ +9E5E0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E5E4 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +9E5E8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E5E9 Created OS 03 (3) 'Unix' │ │ │ │ +9E5EA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E5EB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E5EC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E5EE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E5F0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E5F4 CRC 2E26ED0C (774302988) │ │ │ │ +9E5F8 Compressed Size 00000ACE (2766) │ │ │ │ +9E5FC Uncompressed Size 000022FF (8959) │ │ │ │ +9E600 Filename Length 001B (27) │ │ │ │ +9E602 Extra Length 0018 (24) │ │ │ │ +9E604 Comment Length 0000 (0) │ │ │ │ +9E606 Disk Start 0000 (0) │ │ │ │ +9E608 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E60A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E60E Local Header Offset 00037DEA (228842) │ │ │ │ +9E612 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E612: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E62D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E62F Length 0005 (5) │ │ │ │ +9E631 Flags 01 (1) 'Modification' │ │ │ │ +9E632 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E636 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E638 Length 000B (11) │ │ │ │ +9E63A Version 01 (1) │ │ │ │ +9E63B UID Size 04 (4) │ │ │ │ +9E63C UID 00000000 (0) │ │ │ │ +9E640 GID Size 04 (4) │ │ │ │ +9E641 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E645 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +9E649 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E64A Created OS 03 (3) 'Unix' │ │ │ │ +9E64B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E64C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E64D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E64F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E651 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E655 CRC 944EC433 (2488190003) │ │ │ │ +9E659 Compressed Size 00000A8E (2702) │ │ │ │ +9E65D Uncompressed Size 0000237A (9082) │ │ │ │ +9E661 Filename Length 0013 (19) │ │ │ │ +9E663 Extra Length 0018 (24) │ │ │ │ +9E665 Comment Length 0000 (0) │ │ │ │ +9E667 Disk Start 0000 (0) │ │ │ │ +9E669 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E66B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E66F Local Header Offset 0003890D (231693) │ │ │ │ +9E673 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E673: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E686 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E688 Length 0005 (5) │ │ │ │ +9E68A Flags 01 (1) 'Modification' │ │ │ │ +9E68B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E68F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E691 Length 000B (11) │ │ │ │ +9E693 Version 01 (1) │ │ │ │ +9E694 UID Size 04 (4) │ │ │ │ +9E695 UID 00000000 (0) │ │ │ │ +9E699 GID Size 04 (4) │ │ │ │ +9E69A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E69E CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +9E6A2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E6A3 Created OS 03 (3) 'Unix' │ │ │ │ +9E6A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E6A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E6A6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E6A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E6AA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E6AE CRC 651D575F (1696421727) │ │ │ │ +9E6B2 Compressed Size 00000F47 (3911) │ │ │ │ +9E6B6 Uncompressed Size 000036F1 (14065) │ │ │ │ +9E6BA Filename Length 000F (15) │ │ │ │ +9E6BC Extra Length 0018 (24) │ │ │ │ +9E6BE Comment Length 0000 (0) │ │ │ │ +9E6C0 Disk Start 0000 (0) │ │ │ │ +9E6C2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E6C4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E6C8 Local Header Offset 000393E8 (234472) │ │ │ │ +9E6CC Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E6CC: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E6DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E6DD Length 0005 (5) │ │ │ │ +9E6DF Flags 01 (1) 'Modification' │ │ │ │ +9E6E0 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E6E4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E6E6 Length 000B (11) │ │ │ │ +9E6E8 Version 01 (1) │ │ │ │ +9E6E9 UID Size 04 (4) │ │ │ │ +9E6EA UID 00000000 (0) │ │ │ │ +9E6EE GID Size 04 (4) │ │ │ │ +9E6EF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E6F3 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +9E6F7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E6F8 Created OS 03 (3) 'Unix' │ │ │ │ +9E6F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E6FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E6FB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E6FD Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E6FF Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E703 CRC 54672E6E (1416048238) │ │ │ │ +9E707 Compressed Size 0000066A (1642) │ │ │ │ +9E70B Uncompressed Size 000018DF (6367) │ │ │ │ +9E70F Filename Length 000F (15) │ │ │ │ +9E711 Extra Length 0018 (24) │ │ │ │ +9E713 Comment Length 0000 (0) │ │ │ │ +9E715 Disk Start 0000 (0) │ │ │ │ +9E717 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E719 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E71D Local Header Offset 0003A378 (238456) │ │ │ │ +9E721 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E721: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E730 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E732 Length 0005 (5) │ │ │ │ +9E734 Flags 01 (1) 'Modification' │ │ │ │ +9E735 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E739 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E73B Length 000B (11) │ │ │ │ +9E73D Version 01 (1) │ │ │ │ +9E73E UID Size 04 (4) │ │ │ │ +9E73F UID 00000000 (0) │ │ │ │ +9E743 GID Size 04 (4) │ │ │ │ +9E744 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E748 CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +9E74C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E74D Created OS 03 (3) 'Unix' │ │ │ │ +9E74E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E74F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E750 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E752 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E754 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E758 CRC 0CA4F90A (212138250) │ │ │ │ +9E75C Compressed Size 00001A47 (6727) │ │ │ │ +9E760 Uncompressed Size 000064F2 (25842) │ │ │ │ +9E764 Filename Length 0013 (19) │ │ │ │ +9E766 Extra Length 0018 (24) │ │ │ │ +9E768 Comment Length 0000 (0) │ │ │ │ +9E76A Disk Start 0000 (0) │ │ │ │ +9E76C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E76E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E772 Local Header Offset 0003AA2B (240171) │ │ │ │ +9E776 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E776: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E789 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E78B Length 0005 (5) │ │ │ │ +9E78D Flags 01 (1) 'Modification' │ │ │ │ +9E78E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E792 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E794 Length 000B (11) │ │ │ │ +9E796 Version 01 (1) │ │ │ │ +9E797 UID Size 04 (4) │ │ │ │ +9E798 UID 00000000 (0) │ │ │ │ +9E79C GID Size 04 (4) │ │ │ │ +9E79D GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E7A1 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +9E7A5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E7A6 Created OS 03 (3) 'Unix' │ │ │ │ +9E7A7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E7A8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E7A9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E7AB Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E7AD Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E7B1 CRC D1448544 (3510928708) │ │ │ │ +9E7B5 Compressed Size 000009A5 (2469) │ │ │ │ +9E7B9 Uncompressed Size 00001B64 (7012) │ │ │ │ +9E7BD Filename Length 0010 (16) │ │ │ │ +9E7BF Extra Length 0018 (24) │ │ │ │ +9E7C1 Comment Length 0000 (0) │ │ │ │ +9E7C3 Disk Start 0000 (0) │ │ │ │ +9E7C5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E7C7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E7CB Local Header Offset 0003C4BF (246975) │ │ │ │ +9E7CF Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E7CF: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E7DF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E7E1 Length 0005 (5) │ │ │ │ +9E7E3 Flags 01 (1) 'Modification' │ │ │ │ +9E7E4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E7E8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E7EA Length 000B (11) │ │ │ │ +9E7EC Version 01 (1) │ │ │ │ +9E7ED UID Size 04 (4) │ │ │ │ +9E7EE UID 00000000 (0) │ │ │ │ +9E7F2 GID Size 04 (4) │ │ │ │ +9E7F3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E7F7 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +9E7FB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E7FC Created OS 03 (3) 'Unix' │ │ │ │ +9E7FD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E7FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E7FF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E801 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E803 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E807 CRC 3E5AEB62 (1046145890) │ │ │ │ +9E80B Compressed Size 000006B7 (1719) │ │ │ │ +9E80F Uncompressed Size 00001565 (5477) │ │ │ │ +9E813 Filename Length 0012 (18) │ │ │ │ +9E815 Extra Length 0018 (24) │ │ │ │ +9E817 Comment Length 0000 (0) │ │ │ │ +9E819 Disk Start 0000 (0) │ │ │ │ +9E81B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E81D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E821 Local Header Offset 0003CEAE (249518) │ │ │ │ +9E825 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E825: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E837 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E839 Length 0005 (5) │ │ │ │ +9E83B Flags 01 (1) 'Modification' │ │ │ │ +9E83C Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E840 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E842 Length 000B (11) │ │ │ │ +9E844 Version 01 (1) │ │ │ │ +9E845 UID Size 04 (4) │ │ │ │ +9E846 UID 00000000 (0) │ │ │ │ +9E84A GID Size 04 (4) │ │ │ │ +9E84B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E84F CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ +9E853 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E854 Created OS 03 (3) 'Unix' │ │ │ │ +9E855 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E856 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E857 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E859 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E85B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E85F CRC 8FFD8ADC (2415758044) │ │ │ │ +9E863 Compressed Size 00002A18 (10776) │ │ │ │ +9E867 Uncompressed Size 0000B1C5 (45509) │ │ │ │ +9E86B Filename Length 0010 (16) │ │ │ │ +9E86D Extra Length 0018 (24) │ │ │ │ +9E86F Comment Length 0000 (0) │ │ │ │ +9E871 Disk Start 0000 (0) │ │ │ │ +9E873 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E875 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E879 Local Header Offset 0003D5B1 (251313) │ │ │ │ +9E87D Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E87D: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E88D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E88F Length 0005 (5) │ │ │ │ +9E891 Flags 01 (1) 'Modification' │ │ │ │ +9E892 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E896 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E898 Length 000B (11) │ │ │ │ +9E89A Version 01 (1) │ │ │ │ +9E89B UID Size 04 (4) │ │ │ │ +9E89C UID 00000000 (0) │ │ │ │ +9E8A0 GID Size 04 (4) │ │ │ │ +9E8A1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E8A5 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +9E8A9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E8AA Created OS 03 (3) 'Unix' │ │ │ │ +9E8AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E8AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E8AD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E8AF Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E8B1 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E8B5 CRC 62C4CC1C (1657064476) │ │ │ │ +9E8B9 Compressed Size 00001E8A (7818) │ │ │ │ +9E8BD Uncompressed Size 00009AAA (39594) │ │ │ │ +9E8C1 Filename Length 0012 (18) │ │ │ │ +9E8C3 Extra Length 0018 (24) │ │ │ │ +9E8C5 Comment Length 0000 (0) │ │ │ │ +9E8C7 Disk Start 0000 (0) │ │ │ │ +9E8C9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E8CB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E8CF Local Header Offset 00040013 (262163) │ │ │ │ +9E8D3 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E8D3: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E8E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E8E7 Length 0005 (5) │ │ │ │ +9E8E9 Flags 01 (1) 'Modification' │ │ │ │ +9E8EA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E8EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E8F0 Length 000B (11) │ │ │ │ +9E8F2 Version 01 (1) │ │ │ │ +9E8F3 UID Size 04 (4) │ │ │ │ +9E8F4 UID 00000000 (0) │ │ │ │ +9E8F8 GID Size 04 (4) │ │ │ │ +9E8F9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E8FD CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +9E901 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E902 Created OS 03 (3) 'Unix' │ │ │ │ +9E903 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E904 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E905 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E907 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E909 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E90D CRC 2B1DF12F (723382575) │ │ │ │ +9E911 Compressed Size 0000147C (5244) │ │ │ │ +9E915 Uncompressed Size 00007ACF (31439) │ │ │ │ +9E919 Filename Length 0018 (24) │ │ │ │ +9E91B Extra Length 0018 (24) │ │ │ │ +9E91D Comment Length 0000 (0) │ │ │ │ +9E91F Disk Start 0000 (0) │ │ │ │ +9E921 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E923 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E927 Local Header Offset 00041EE9 (270057) │ │ │ │ +9E92B Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E92B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E943 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E945 Length 0005 (5) │ │ │ │ +9E947 Flags 01 (1) 'Modification' │ │ │ │ +9E948 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E94C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E94E Length 000B (11) │ │ │ │ +9E950 Version 01 (1) │ │ │ │ +9E951 UID Size 04 (4) │ │ │ │ +9E952 UID 00000000 (0) │ │ │ │ +9E956 GID Size 04 (4) │ │ │ │ +9E957 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E95B CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +9E95F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E960 Created OS 03 (3) 'Unix' │ │ │ │ +9E961 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E962 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E963 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E965 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E967 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E96B CRC 0BA86E64 (195587684) │ │ │ │ +9E96F Compressed Size 000018D4 (6356) │ │ │ │ +9E973 Uncompressed Size 0000A7F4 (42996) │ │ │ │ +9E977 Filename Length 001F (31) │ │ │ │ +9E979 Extra Length 0018 (24) │ │ │ │ +9E97B Comment Length 0000 (0) │ │ │ │ +9E97D Disk Start 0000 (0) │ │ │ │ +9E97F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E981 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E985 Local Header Offset 000433B7 (275383) │ │ │ │ +9E989 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E989: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9E9A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9E9AA Length 0005 (5) │ │ │ │ +9E9AC Flags 01 (1) 'Modification' │ │ │ │ +9E9AD Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9E9B1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9E9B3 Length 000B (11) │ │ │ │ +9E9B5 Version 01 (1) │ │ │ │ +9E9B6 UID Size 04 (4) │ │ │ │ +9E9B7 UID 00000000 (0) │ │ │ │ +9E9BB GID Size 04 (4) │ │ │ │ +9E9BC GID 00000000 (0) │ │ │ │ + │ │ │ │ +9E9C0 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +9E9C4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9E9C5 Created OS 03 (3) 'Unix' │ │ │ │ +9E9C6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9E9C7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9E9C8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9E9CA Compression Method 0008 (8) 'Deflated' │ │ │ │ +9E9CC Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9E9D0 CRC FFAF198F (4289665423) │ │ │ │ +9E9D4 Compressed Size 000003F6 (1014) │ │ │ │ +9E9D8 Uncompressed Size 000008A3 (2211) │ │ │ │ +9E9DC Filename Length 001E (30) │ │ │ │ +9E9DE Extra Length 0018 (24) │ │ │ │ +9E9E0 Comment Length 0000 (0) │ │ │ │ +9E9E2 Disk Start 0000 (0) │ │ │ │ +9E9E4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9E9E6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9E9EA Local Header Offset 00044CE4 (281828) │ │ │ │ +9E9EE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9E9EE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EA0C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EA0E Length 0005 (5) │ │ │ │ +9EA10 Flags 01 (1) 'Modification' │ │ │ │ +9EA11 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EA15 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EA17 Length 000B (11) │ │ │ │ +9EA19 Version 01 (1) │ │ │ │ +9EA1A UID Size 04 (4) │ │ │ │ +9EA1B UID 00000000 (0) │ │ │ │ +9EA1F GID Size 04 (4) │ │ │ │ +9EA20 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EA24 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +9EA28 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EA29 Created OS 03 (3) 'Unix' │ │ │ │ +9EA2A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EA2B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EA2C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EA2E Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EA30 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EA34 CRC 32E3FA13 (853801491) │ │ │ │ +9EA38 Compressed Size 00004292 (17042) │ │ │ │ +9EA3C Uncompressed Size 0000D8DC (55516) │ │ │ │ +9EA40 Filename Length 0013 (19) │ │ │ │ +9EA42 Extra Length 0018 (24) │ │ │ │ +9EA44 Comment Length 0000 (0) │ │ │ │ +9EA46 Disk Start 0000 (0) │ │ │ │ +9EA48 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EA4A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EA4E Local Header Offset 00045132 (282930) │ │ │ │ +9EA52 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EA52: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EA65 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EA67 Length 0005 (5) │ │ │ │ +9EA69 Flags 01 (1) 'Modification' │ │ │ │ +9EA6A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EA6E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EA70 Length 000B (11) │ │ │ │ +9EA72 Version 01 (1) │ │ │ │ +9EA73 UID Size 04 (4) │ │ │ │ +9EA74 UID 00000000 (0) │ │ │ │ +9EA78 GID Size 04 (4) │ │ │ │ +9EA79 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EA7D CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +9EA81 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EA82 Created OS 03 (3) 'Unix' │ │ │ │ +9EA83 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EA84 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EA85 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EA87 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EA89 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EA8D CRC 71753995 (1903507861) │ │ │ │ +9EA91 Compressed Size 000026C4 (9924) │ │ │ │ +9EA95 Uncompressed Size 00006E45 (28229) │ │ │ │ +9EA99 Filename Length 0019 (25) │ │ │ │ +9EA9B Extra Length 0018 (24) │ │ │ │ +9EA9D Comment Length 0000 (0) │ │ │ │ +9EA9F Disk Start 0000 (0) │ │ │ │ +9EAA1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EAA3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EAA7 Local Header Offset 00049411 (300049) │ │ │ │ +9EAAB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EAAB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EAC4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EAC6 Length 0005 (5) │ │ │ │ +9EAC8 Flags 01 (1) 'Modification' │ │ │ │ +9EAC9 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EACD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EACF Length 000B (11) │ │ │ │ +9EAD1 Version 01 (1) │ │ │ │ +9EAD2 UID Size 04 (4) │ │ │ │ +9EAD3 UID 00000000 (0) │ │ │ │ +9EAD7 GID Size 04 (4) │ │ │ │ +9EAD8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EADC CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +9EAE0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EAE1 Created OS 03 (3) 'Unix' │ │ │ │ +9EAE2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EAE3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EAE4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EAE6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EAE8 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EAEC CRC 0369577C (57235324) │ │ │ │ +9EAF0 Compressed Size 00002739 (10041) │ │ │ │ +9EAF4 Uncompressed Size 00008B83 (35715) │ │ │ │ +9EAF8 Filename Length 0019 (25) │ │ │ │ +9EAFA Extra Length 0018 (24) │ │ │ │ +9EAFC Comment Length 0000 (0) │ │ │ │ +9EAFE Disk Start 0000 (0) │ │ │ │ +9EB00 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EB02 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EB06 Local Header Offset 0004BB28 (310056) │ │ │ │ +9EB0A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EB0A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EB23 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EB25 Length 0005 (5) │ │ │ │ +9EB27 Flags 01 (1) 'Modification' │ │ │ │ +9EB28 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EB2C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EB2E Length 000B (11) │ │ │ │ +9EB30 Version 01 (1) │ │ │ │ +9EB31 UID Size 04 (4) │ │ │ │ +9EB32 UID 00000000 (0) │ │ │ │ +9EB36 GID Size 04 (4) │ │ │ │ +9EB37 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EB3B CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +9EB3F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EB40 Created OS 03 (3) 'Unix' │ │ │ │ +9EB41 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EB42 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EB43 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EB45 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EB47 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EB4B CRC A05E06F6 (2690516726) │ │ │ │ +9EB4F Compressed Size 00000CF1 (3313) │ │ │ │ +9EB53 Uncompressed Size 0000517A (20858) │ │ │ │ +9EB57 Filename Length 0021 (33) │ │ │ │ +9EB59 Extra Length 0018 (24) │ │ │ │ +9EB5B Comment Length 0000 (0) │ │ │ │ +9EB5D Disk Start 0000 (0) │ │ │ │ +9EB5F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EB61 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EB65 Local Header Offset 0004E2B4 (320180) │ │ │ │ +9EB69 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EB69: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EB8A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EB8C Length 0005 (5) │ │ │ │ +9EB8E Flags 01 (1) 'Modification' │ │ │ │ +9EB8F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EB93 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EB95 Length 000B (11) │ │ │ │ +9EB97 Version 01 (1) │ │ │ │ +9EB98 UID Size 04 (4) │ │ │ │ +9EB99 UID 00000000 (0) │ │ │ │ +9EB9D GID Size 04 (4) │ │ │ │ +9EB9E GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EBA2 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +9EBA6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EBA7 Created OS 03 (3) 'Unix' │ │ │ │ +9EBA8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EBA9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EBAA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EBAC Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EBAE Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EBB2 CRC 8F01E69C (2399266460) │ │ │ │ +9EBB6 Compressed Size 00000468 (1128) │ │ │ │ +9EBBA Uncompressed Size 00000931 (2353) │ │ │ │ +9EBBE Filename Length 001B (27) │ │ │ │ +9EBC0 Extra Length 0018 (24) │ │ │ │ +9EBC2 Comment Length 0000 (0) │ │ │ │ +9EBC4 Disk Start 0000 (0) │ │ │ │ +9EBC6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EBC8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EBCC Local Header Offset 0004F000 (323584) │ │ │ │ +9EBD0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EBD0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EBEB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EBED Length 0005 (5) │ │ │ │ +9EBEF Flags 01 (1) 'Modification' │ │ │ │ +9EBF0 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EBF4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EBF6 Length 000B (11) │ │ │ │ +9EBF8 Version 01 (1) │ │ │ │ +9EBF9 UID Size 04 (4) │ │ │ │ +9EBFA UID 00000000 (0) │ │ │ │ +9EBFE GID Size 04 (4) │ │ │ │ +9EBFF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EC03 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +9EC07 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EC08 Created OS 03 (3) 'Unix' │ │ │ │ +9EC09 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EC0A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EC0B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EC0D Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EC0F Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EC13 CRC 26F2BE77 (653442679) │ │ │ │ +9EC17 Compressed Size 000016F0 (5872) │ │ │ │ +9EC1B Uncompressed Size 00007A6D (31341) │ │ │ │ +9EC1F Filename Length 001F (31) │ │ │ │ +9EC21 Extra Length 0018 (24) │ │ │ │ +9EC23 Comment Length 0000 (0) │ │ │ │ +9EC25 Disk Start 0000 (0) │ │ │ │ +9EC27 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EC29 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EC2D Local Header Offset 0004F4BD (324797) │ │ │ │ +9EC31 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EC31: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EC50 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EC52 Length 0005 (5) │ │ │ │ +9EC54 Flags 01 (1) 'Modification' │ │ │ │ +9EC55 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EC59 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EC5B Length 000B (11) │ │ │ │ +9EC5D Version 01 (1) │ │ │ │ +9EC5E UID Size 04 (4) │ │ │ │ +9EC5F UID 00000000 (0) │ │ │ │ +9EC63 GID Size 04 (4) │ │ │ │ +9EC64 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EC68 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +9EC6C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EC6D Created OS 03 (3) 'Unix' │ │ │ │ +9EC6E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EC6F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EC70 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EC72 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EC74 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EC78 CRC 11C5667D (298149501) │ │ │ │ +9EC7C Compressed Size 00004166 (16742) │ │ │ │ +9EC80 Uncompressed Size 0001CF93 (118675) │ │ │ │ +9EC84 Filename Length 0010 (16) │ │ │ │ +9EC86 Extra Length 0018 (24) │ │ │ │ +9EC88 Comment Length 0000 (0) │ │ │ │ +9EC8A Disk Start 0000 (0) │ │ │ │ +9EC8C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EC8E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EC92 Local Header Offset 00050C06 (330758) │ │ │ │ +9EC96 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EC96: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9ECA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9ECA8 Length 0005 (5) │ │ │ │ +9ECAA Flags 01 (1) 'Modification' │ │ │ │ +9ECAB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9ECAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9ECB1 Length 000B (11) │ │ │ │ +9ECB3 Version 01 (1) │ │ │ │ +9ECB4 UID Size 04 (4) │ │ │ │ +9ECB5 UID 00000000 (0) │ │ │ │ +9ECB9 GID Size 04 (4) │ │ │ │ +9ECBA GID 00000000 (0) │ │ │ │ + │ │ │ │ +9ECBE CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +9ECC2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9ECC3 Created OS 03 (3) 'Unix' │ │ │ │ +9ECC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9ECC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9ECC6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9ECC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9ECCA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9ECCE CRC 2020DAF6 (539024118) │ │ │ │ +9ECD2 Compressed Size 00000A94 (2708) │ │ │ │ +9ECD6 Uncompressed Size 00002105 (8453) │ │ │ │ +9ECDA Filename Length 0014 (20) │ │ │ │ +9ECDC Extra Length 0018 (24) │ │ │ │ +9ECDE Comment Length 0000 (0) │ │ │ │ +9ECE0 Disk Start 0000 (0) │ │ │ │ +9ECE2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9ECE4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9ECE8 Local Header Offset 00054DB6 (347574) │ │ │ │ +9ECEC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9ECEC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9ED00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9ED02 Length 0005 (5) │ │ │ │ +9ED04 Flags 01 (1) 'Modification' │ │ │ │ +9ED05 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9ED09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9ED0B Length 000B (11) │ │ │ │ +9ED0D Version 01 (1) │ │ │ │ +9ED0E UID Size 04 (4) │ │ │ │ +9ED0F UID 00000000 (0) │ │ │ │ +9ED13 GID Size 04 (4) │ │ │ │ +9ED14 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9ED18 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +9ED1C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9ED1D Created OS 03 (3) 'Unix' │ │ │ │ +9ED1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9ED1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9ED20 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9ED22 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9ED24 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9ED28 CRC 1EA2D694 (513988244) │ │ │ │ +9ED2C Compressed Size 0000AC9E (44190) │ │ │ │ +9ED30 Uncompressed Size 0003E418 (255000) │ │ │ │ +9ED34 Filename Length 0017 (23) │ │ │ │ +9ED36 Extra Length 0018 (24) │ │ │ │ +9ED38 Comment Length 0000 (0) │ │ │ │ +9ED3A Disk Start 0000 (0) │ │ │ │ +9ED3C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9ED3E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9ED42 Local Header Offset 00055898 (350360) │ │ │ │ +9ED46 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9ED46: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9ED5D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9ED5F Length 0005 (5) │ │ │ │ +9ED61 Flags 01 (1) 'Modification' │ │ │ │ +9ED62 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9ED66 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9ED68 Length 000B (11) │ │ │ │ +9ED6A Version 01 (1) │ │ │ │ +9ED6B UID Size 04 (4) │ │ │ │ +9ED6C UID 00000000 (0) │ │ │ │ +9ED70 GID Size 04 (4) │ │ │ │ +9ED71 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9ED75 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +9ED79 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9ED7A Created OS 03 (3) 'Unix' │ │ │ │ +9ED7B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9ED7C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9ED7D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9ED7F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9ED81 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9ED85 CRC F3542EE5 (4082380517) │ │ │ │ +9ED89 Compressed Size 00000401 (1025) │ │ │ │ +9ED8D Uncompressed Size 0000093D (2365) │ │ │ │ +9ED91 Filename Length 0013 (19) │ │ │ │ +9ED93 Extra Length 0018 (24) │ │ │ │ +9ED95 Comment Length 0000 (0) │ │ │ │ +9ED97 Disk Start 0000 (0) │ │ │ │ +9ED99 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9ED9B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9ED9F Local Header Offset 00060587 (394631) │ │ │ │ +9EDA3 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EDA3: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EDB6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EDB8 Length 0005 (5) │ │ │ │ +9EDBA Flags 01 (1) 'Modification' │ │ │ │ +9EDBB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EDBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EDC1 Length 000B (11) │ │ │ │ +9EDC3 Version 01 (1) │ │ │ │ +9EDC4 UID Size 04 (4) │ │ │ │ +9EDC5 UID 00000000 (0) │ │ │ │ +9EDC9 GID Size 04 (4) │ │ │ │ +9EDCA GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EDCE CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +9EDD2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EDD3 Created OS 03 (3) 'Unix' │ │ │ │ +9EDD4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EDD5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EDD6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EDD8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EDDA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EDDE CRC 242FEA37 (607119927) │ │ │ │ +9EDE2 Compressed Size 000014E5 (5349) │ │ │ │ +9EDE6 Uncompressed Size 0000687B (26747) │ │ │ │ +9EDEA Filename Length 0012 (18) │ │ │ │ +9EDEC Extra Length 0018 (24) │ │ │ │ +9EDEE Comment Length 0000 (0) │ │ │ │ +9EDF0 Disk Start 0000 (0) │ │ │ │ +9EDF2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EDF4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EDF8 Local Header Offset 000609D5 (395733) │ │ │ │ +9EDFC Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EDFC: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EE0E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EE10 Length 0005 (5) │ │ │ │ +9EE12 Flags 01 (1) 'Modification' │ │ │ │ +9EE13 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EE17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EE19 Length 000B (11) │ │ │ │ +9EE1B Version 01 (1) │ │ │ │ +9EE1C UID Size 04 (4) │ │ │ │ +9EE1D UID 00000000 (0) │ │ │ │ +9EE21 GID Size 04 (4) │ │ │ │ +9EE22 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EE26 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +9EE2A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EE2B Created OS 03 (3) 'Unix' │ │ │ │ +9EE2C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EE2D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EE2E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EE30 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EE32 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EE36 CRC A41464A8 (2752799912) │ │ │ │ +9EE3A Compressed Size 000011EB (4587) │ │ │ │ +9EE3E Uncompressed Size 000040F5 (16629) │ │ │ │ +9EE42 Filename Length 0012 (18) │ │ │ │ +9EE44 Extra Length 0018 (24) │ │ │ │ +9EE46 Comment Length 0000 (0) │ │ │ │ +9EE48 Disk Start 0000 (0) │ │ │ │ +9EE4A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EE4C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EE50 Local Header Offset 00061F06 (401158) │ │ │ │ +9EE54 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EE54: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EE66 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EE68 Length 0005 (5) │ │ │ │ +9EE6A Flags 01 (1) 'Modification' │ │ │ │ +9EE6B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EE6F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EE71 Length 000B (11) │ │ │ │ +9EE73 Version 01 (1) │ │ │ │ +9EE74 UID Size 04 (4) │ │ │ │ +9EE75 UID 00000000 (0) │ │ │ │ +9EE79 GID Size 04 (4) │ │ │ │ +9EE7A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EE7E CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +9EE82 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EE83 Created OS 03 (3) 'Unix' │ │ │ │ +9EE84 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EE85 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EE86 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EE88 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EE8A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EE8E CRC 39168DC1 (957779393) │ │ │ │ +9EE92 Compressed Size 000009DA (2522) │ │ │ │ +9EE96 Uncompressed Size 00003529 (13609) │ │ │ │ +9EE9A Filename Length 0019 (25) │ │ │ │ +9EE9C Extra Length 0018 (24) │ │ │ │ +9EE9E Comment Length 0000 (0) │ │ │ │ +9EEA0 Disk Start 0000 (0) │ │ │ │ +9EEA2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EEA4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EEA8 Local Header Offset 0006313D (405821) │ │ │ │ +9EEAC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EEAC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EEC5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EEC7 Length 0005 (5) │ │ │ │ +9EEC9 Flags 01 (1) 'Modification' │ │ │ │ +9EECA Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EECE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EED0 Length 000B (11) │ │ │ │ +9EED2 Version 01 (1) │ │ │ │ +9EED3 UID Size 04 (4) │ │ │ │ +9EED4 UID 00000000 (0) │ │ │ │ +9EED8 GID Size 04 (4) │ │ │ │ +9EED9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EEDD CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +9EEE1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EEE2 Created OS 03 (3) 'Unix' │ │ │ │ +9EEE3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EEE4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EEE5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EEE7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EEE9 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EEED CRC 65AF560D (1705989645) │ │ │ │ +9EEF1 Compressed Size 000018AD (6317) │ │ │ │ +9EEF5 Uncompressed Size 0000A605 (42501) │ │ │ │ +9EEF9 Filename Length 0019 (25) │ │ │ │ +9EEFB Extra Length 0018 (24) │ │ │ │ +9EEFD Comment Length 0000 (0) │ │ │ │ +9EEFF Disk Start 0000 (0) │ │ │ │ +9EF01 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EF03 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EF07 Local Header Offset 00063B6A (408426) │ │ │ │ +9EF0B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EF0B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EF24 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EF26 Length 0005 (5) │ │ │ │ +9EF28 Flags 01 (1) 'Modification' │ │ │ │ +9EF29 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EF2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EF2F Length 000B (11) │ │ │ │ +9EF31 Version 01 (1) │ │ │ │ +9EF32 UID Size 04 (4) │ │ │ │ +9EF33 UID 00000000 (0) │ │ │ │ +9EF37 GID Size 04 (4) │ │ │ │ +9EF38 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EF3C CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +9EF40 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EF41 Created OS 03 (3) 'Unix' │ │ │ │ +9EF42 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EF43 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EF44 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EF46 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EF48 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EF4C CRC 47E0D6DC (1205917404) │ │ │ │ +9EF50 Compressed Size 0000177E (6014) │ │ │ │ +9EF54 Uncompressed Size 0000472C (18220) │ │ │ │ +9EF58 Filename Length 0014 (20) │ │ │ │ +9EF5A Extra Length 0018 (24) │ │ │ │ +9EF5C Comment Length 0000 (0) │ │ │ │ +9EF5E Disk Start 0000 (0) │ │ │ │ +9EF60 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EF62 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EF66 Local Header Offset 0006546A (414826) │ │ │ │ +9EF6A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EF6A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EF7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EF80 Length 0005 (5) │ │ │ │ +9EF82 Flags 01 (1) 'Modification' │ │ │ │ +9EF83 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EF87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EF89 Length 000B (11) │ │ │ │ +9EF8B Version 01 (1) │ │ │ │ +9EF8C UID Size 04 (4) │ │ │ │ +9EF8D UID 00000000 (0) │ │ │ │ +9EF91 GID Size 04 (4) │ │ │ │ +9EF92 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EF96 CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +9EF9A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EF9B Created OS 03 (3) 'Unix' │ │ │ │ +9EF9C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EF9D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9EF9E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9EFA0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9EFA2 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9EFA6 CRC EDF51269 (3992261225) │ │ │ │ +9EFAA Compressed Size 0000040B (1035) │ │ │ │ +9EFAE Uncompressed Size 00000825 (2085) │ │ │ │ +9EFB2 Filename Length 001C (28) │ │ │ │ +9EFB4 Extra Length 0018 (24) │ │ │ │ +9EFB6 Comment Length 0000 (0) │ │ │ │ +9EFB8 Disk Start 0000 (0) │ │ │ │ +9EFBA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9EFBC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9EFC0 Local Header Offset 00066C36 (420918) │ │ │ │ +9EFC4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9EFC4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9EFE0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9EFE2 Length 0005 (5) │ │ │ │ +9EFE4 Flags 01 (1) 'Modification' │ │ │ │ +9EFE5 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9EFE9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9EFEB Length 000B (11) │ │ │ │ +9EFED Version 01 (1) │ │ │ │ +9EFEE UID Size 04 (4) │ │ │ │ +9EFEF UID 00000000 (0) │ │ │ │ +9EFF3 GID Size 04 (4) │ │ │ │ +9EFF4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9EFF8 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +9EFFC Created Zip Spec 3D (61) '6.1' │ │ │ │ +9EFFD Created OS 03 (3) 'Unix' │ │ │ │ +9EFFE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9EFFF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F000 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F002 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F004 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F008 CRC 2BC65F52 (734420818) │ │ │ │ +9F00C Compressed Size 0000247D (9341) │ │ │ │ +9F010 Uncompressed Size 0000B56F (46447) │ │ │ │ +9F014 Filename Length 001F (31) │ │ │ │ +9F016 Extra Length 0018 (24) │ │ │ │ +9F018 Comment Length 0000 (0) │ │ │ │ +9F01A Disk Start 0000 (0) │ │ │ │ +9F01C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F01E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F022 Local Header Offset 00067097 (422039) │ │ │ │ +9F026 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F026: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F045 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F047 Length 0005 (5) │ │ │ │ +9F049 Flags 01 (1) 'Modification' │ │ │ │ +9F04A Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F04E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F050 Length 000B (11) │ │ │ │ +9F052 Version 01 (1) │ │ │ │ +9F053 UID Size 04 (4) │ │ │ │ +9F054 UID 00000000 (0) │ │ │ │ +9F058 GID Size 04 (4) │ │ │ │ +9F059 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F05D CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +9F061 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F062 Created OS 03 (3) 'Unix' │ │ │ │ +9F063 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F064 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F065 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F067 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F069 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F06D CRC 0B9BF55C (194770268) │ │ │ │ +9F071 Compressed Size 00000E7C (3708) │ │ │ │ +9F075 Uncompressed Size 000052D9 (21209) │ │ │ │ +9F079 Filename Length 001F (31) │ │ │ │ +9F07B Extra Length 0018 (24) │ │ │ │ +9F07D Comment Length 0000 (0) │ │ │ │ +9F07F Disk Start 0000 (0) │ │ │ │ +9F081 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F083 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F087 Local Header Offset 0006956D (431469) │ │ │ │ +9F08B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F08B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F0AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F0AC Length 0005 (5) │ │ │ │ +9F0AE Flags 01 (1) 'Modification' │ │ │ │ +9F0AF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F0B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F0B5 Length 000B (11) │ │ │ │ +9F0B7 Version 01 (1) │ │ │ │ +9F0B8 UID Size 04 (4) │ │ │ │ +9F0B9 UID 00000000 (0) │ │ │ │ +9F0BD GID Size 04 (4) │ │ │ │ +9F0BE GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F0C2 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +9F0C6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F0C7 Created OS 03 (3) 'Unix' │ │ │ │ +9F0C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F0C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F0CA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F0CC Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F0CE Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F0D2 CRC 535A7770 (1398437744) │ │ │ │ +9F0D6 Compressed Size 00000A44 (2628) │ │ │ │ +9F0DA Uncompressed Size 0000247A (9338) │ │ │ │ +9F0DE Filename Length 0013 (19) │ │ │ │ +9F0E0 Extra Length 0018 (24) │ │ │ │ +9F0E2 Comment Length 0000 (0) │ │ │ │ +9F0E4 Disk Start 0000 (0) │ │ │ │ +9F0E6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F0E8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F0EC Local Header Offset 0006A442 (435266) │ │ │ │ +9F0F0 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F0F0: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F103 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F105 Length 0005 (5) │ │ │ │ +9F107 Flags 01 (1) 'Modification' │ │ │ │ +9F108 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F10C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F10E Length 000B (11) │ │ │ │ +9F110 Version 01 (1) │ │ │ │ +9F111 UID Size 04 (4) │ │ │ │ +9F112 UID 00000000 (0) │ │ │ │ +9F116 GID Size 04 (4) │ │ │ │ +9F117 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F11B CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +9F11F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F120 Created OS 03 (3) 'Unix' │ │ │ │ +9F121 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F122 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F123 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F125 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F127 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F12B CRC FDE5E6FA (4259702522) │ │ │ │ +9F12F Compressed Size 00002484 (9348) │ │ │ │ +9F133 Uncompressed Size 0000B84C (47180) │ │ │ │ +9F137 Filename Length 0019 (25) │ │ │ │ +9F139 Extra Length 0018 (24) │ │ │ │ +9F13B Comment Length 0000 (0) │ │ │ │ +9F13D Disk Start 0000 (0) │ │ │ │ +9F13F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F141 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F145 Local Header Offset 0006AED3 (437971) │ │ │ │ +9F149 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F149: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F162 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F164 Length 0005 (5) │ │ │ │ +9F166 Flags 01 (1) 'Modification' │ │ │ │ +9F167 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F16B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F16D Length 000B (11) │ │ │ │ +9F16F Version 01 (1) │ │ │ │ +9F170 UID Size 04 (4) │ │ │ │ +9F171 UID 00000000 (0) │ │ │ │ +9F175 GID Size 04 (4) │ │ │ │ +9F176 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F17A CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +9F17E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F17F Created OS 03 (3) 'Unix' │ │ │ │ +9F180 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F181 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F182 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F184 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F186 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F18A CRC 645CB06A (1683796074) │ │ │ │ +9F18E Compressed Size 00000EF9 (3833) │ │ │ │ +9F192 Uncompressed Size 00003A2C (14892) │ │ │ │ +9F196 Filename Length 0024 (36) │ │ │ │ +9F198 Extra Length 0018 (24) │ │ │ │ +9F19A Comment Length 0000 (0) │ │ │ │ +9F19C Disk Start 0000 (0) │ │ │ │ +9F19E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F1A0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F1A4 Local Header Offset 0006D3AA (447402) │ │ │ │ +9F1A8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F1A8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F1CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F1CE Length 0005 (5) │ │ │ │ +9F1D0 Flags 01 (1) 'Modification' │ │ │ │ +9F1D1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F1D5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F1D7 Length 000B (11) │ │ │ │ +9F1D9 Version 01 (1) │ │ │ │ +9F1DA UID Size 04 (4) │ │ │ │ +9F1DB UID 00000000 (0) │ │ │ │ +9F1DF GID Size 04 (4) │ │ │ │ +9F1E0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F1E4 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +9F1E8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F1E9 Created OS 03 (3) 'Unix' │ │ │ │ +9F1EA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F1EB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F1EC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F1EE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F1F0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F1F4 CRC 24DED018 (618582040) │ │ │ │ +9F1F8 Compressed Size 00001AC1 (6849) │ │ │ │ +9F1FC Uncompressed Size 00005EDC (24284) │ │ │ │ +9F200 Filename Length 0017 (23) │ │ │ │ +9F202 Extra Length 0018 (24) │ │ │ │ +9F204 Comment Length 0000 (0) │ │ │ │ +9F206 Disk Start 0000 (0) │ │ │ │ +9F208 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F20A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F20E Local Header Offset 0006E301 (451329) │ │ │ │ +9F212 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F212: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F229 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F22B Length 0005 (5) │ │ │ │ +9F22D Flags 01 (1) 'Modification' │ │ │ │ +9F22E Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F232 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F234 Length 000B (11) │ │ │ │ +9F236 Version 01 (1) │ │ │ │ +9F237 UID Size 04 (4) │ │ │ │ +9F238 UID 00000000 (0) │ │ │ │ +9F23C GID Size 04 (4) │ │ │ │ +9F23D GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F241 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +9F245 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F246 Created OS 03 (3) 'Unix' │ │ │ │ +9F247 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F248 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F249 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F24B Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F24D Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F251 CRC 11E32AF1 (300100337) │ │ │ │ +9F255 Compressed Size 00000ED3 (3795) │ │ │ │ +9F259 Uncompressed Size 000038E2 (14562) │ │ │ │ +9F25D Filename Length 0023 (35) │ │ │ │ +9F25F Extra Length 0018 (24) │ │ │ │ +9F261 Comment Length 0000 (0) │ │ │ │ +9F263 Disk Start 0000 (0) │ │ │ │ +9F265 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F267 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F26B Local Header Offset 0006FE13 (458259) │ │ │ │ +9F26F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F26F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F292 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F294 Length 0005 (5) │ │ │ │ +9F296 Flags 01 (1) 'Modification' │ │ │ │ +9F297 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F29B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F29D Length 000B (11) │ │ │ │ +9F29F Version 01 (1) │ │ │ │ +9F2A0 UID Size 04 (4) │ │ │ │ +9F2A1 UID 00000000 (0) │ │ │ │ +9F2A5 GID Size 04 (4) │ │ │ │ +9F2A6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F2AA CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +9F2AE Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F2AF Created OS 03 (3) 'Unix' │ │ │ │ +9F2B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F2B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F2B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F2B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F2B6 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F2BA CRC 2DB7929F (767005343) │ │ │ │ +9F2BE Compressed Size 00000113 (275) │ │ │ │ +9F2C2 Uncompressed Size 000001F3 (499) │ │ │ │ +9F2C6 Filename Length 001B (27) │ │ │ │ +9F2C8 Extra Length 0018 (24) │ │ │ │ +9F2CA Comment Length 0000 (0) │ │ │ │ +9F2CC Disk Start 0000 (0) │ │ │ │ +9F2CE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F2D0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F2D4 Local Header Offset 00070D43 (462147) │ │ │ │ +9F2D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F2D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F2F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F2F5 Length 0005 (5) │ │ │ │ +9F2F7 Flags 01 (1) 'Modification' │ │ │ │ +9F2F8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F2FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F2FE Length 000B (11) │ │ │ │ +9F300 Version 01 (1) │ │ │ │ +9F301 UID Size 04 (4) │ │ │ │ +9F302 UID 00000000 (0) │ │ │ │ +9F306 GID Size 04 (4) │ │ │ │ +9F307 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F30B CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +9F30F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F310 Created OS 03 (3) 'Unix' │ │ │ │ +9F311 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F312 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F313 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F315 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F317 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F31B CRC 62D11409 (1657869321) │ │ │ │ +9F31F Compressed Size 00001892 (6290) │ │ │ │ +9F323 Uncompressed Size 00008FAC (36780) │ │ │ │ +9F327 Filename Length 001D (29) │ │ │ │ +9F329 Extra Length 0018 (24) │ │ │ │ +9F32B Comment Length 0000 (0) │ │ │ │ +9F32D Disk Start 0000 (0) │ │ │ │ +9F32F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F331 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F335 Local Header Offset 00070EAB (462507) │ │ │ │ +9F339 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F339: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F356 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F358 Length 0005 (5) │ │ │ │ +9F35A Flags 01 (1) 'Modification' │ │ │ │ +9F35B Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F35F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F361 Length 000B (11) │ │ │ │ +9F363 Version 01 (1) │ │ │ │ +9F364 UID Size 04 (4) │ │ │ │ +9F365 UID 00000000 (0) │ │ │ │ +9F369 GID Size 04 (4) │ │ │ │ +9F36A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F36E CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +9F372 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F373 Created OS 03 (3) 'Unix' │ │ │ │ +9F374 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F375 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F376 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F378 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F37A Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F37E CRC 63FED801 (1677645825) │ │ │ │ +9F382 Compressed Size 0000164D (5709) │ │ │ │ +9F386 Uncompressed Size 00003A9B (15003) │ │ │ │ +9F38A Filename Length 0015 (21) │ │ │ │ +9F38C Extra Length 0018 (24) │ │ │ │ +9F38E Comment Length 0000 (0) │ │ │ │ +9F390 Disk Start 0000 (0) │ │ │ │ +9F392 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F394 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F398 Local Header Offset 00072794 (468884) │ │ │ │ +9F39C Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F39C: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F3B1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F3B3 Length 0005 (5) │ │ │ │ +9F3B5 Flags 01 (1) 'Modification' │ │ │ │ +9F3B6 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F3BA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F3BC Length 000B (11) │ │ │ │ +9F3BE Version 01 (1) │ │ │ │ +9F3BF UID Size 04 (4) │ │ │ │ +9F3C0 UID 00000000 (0) │ │ │ │ +9F3C4 GID Size 04 (4) │ │ │ │ +9F3C5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F3C9 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +9F3CD Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F3CE Created OS 03 (3) 'Unix' │ │ │ │ +9F3CF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F3D0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F3D1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F3D3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F3D5 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F3D9 CRC 3B61973B (996251451) │ │ │ │ +9F3DD Compressed Size 00003B53 (15187) │ │ │ │ +9F3E1 Uncompressed Size 0001185B (71771) │ │ │ │ +9F3E5 Filename Length 0016 (22) │ │ │ │ +9F3E7 Extra Length 0018 (24) │ │ │ │ +9F3E9 Comment Length 0000 (0) │ │ │ │ +9F3EB Disk Start 0000 (0) │ │ │ │ +9F3ED Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F3EF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F3F3 Local Header Offset 00073E30 (474672) │ │ │ │ +9F3F7 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F3F7: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F40D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F40F Length 0005 (5) │ │ │ │ +9F411 Flags 01 (1) 'Modification' │ │ │ │ +9F412 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F416 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F418 Length 000B (11) │ │ │ │ +9F41A Version 01 (1) │ │ │ │ +9F41B UID Size 04 (4) │ │ │ │ +9F41C UID 00000000 (0) │ │ │ │ +9F420 GID Size 04 (4) │ │ │ │ +9F421 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F425 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +9F429 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F42A Created OS 03 (3) 'Unix' │ │ │ │ +9F42B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F42C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F42D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F42F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F431 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F435 CRC 69B4C594 (1773454740) │ │ │ │ +9F439 Compressed Size 00003E87 (16007) │ │ │ │ +9F43D Uncompressed Size 0001C17B (115067) │ │ │ │ +9F441 Filename Length 0019 (25) │ │ │ │ +9F443 Extra Length 0018 (24) │ │ │ │ +9F445 Comment Length 0000 (0) │ │ │ │ +9F447 Disk Start 0000 (0) │ │ │ │ +9F449 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F44B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F44F Local Header Offset 000779D3 (489939) │ │ │ │ +9F453 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F453: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F46C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F46E Length 0005 (5) │ │ │ │ +9F470 Flags 01 (1) 'Modification' │ │ │ │ +9F471 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F475 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F477 Length 000B (11) │ │ │ │ +9F479 Version 01 (1) │ │ │ │ +9F47A UID Size 04 (4) │ │ │ │ +9F47B UID 00000000 (0) │ │ │ │ +9F47F GID Size 04 (4) │ │ │ │ +9F480 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F484 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +9F488 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F489 Created OS 03 (3) 'Unix' │ │ │ │ +9F48A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F48B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F48C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F48E Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F490 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F494 CRC 49B1A3B3 (1236378547) │ │ │ │ +9F498 Compressed Size 00000837 (2103) │ │ │ │ +9F49C Uncompressed Size 00003383 (13187) │ │ │ │ +9F4A0 Filename Length 0011 (17) │ │ │ │ +9F4A2 Extra Length 0018 (24) │ │ │ │ +9F4A4 Comment Length 0000 (0) │ │ │ │ +9F4A6 Disk Start 0000 (0) │ │ │ │ +9F4A8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F4AA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F4AE Local Header Offset 0007B8AD (506029) │ │ │ │ +9F4B2 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F4B2: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F4C3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F4C5 Length 0005 (5) │ │ │ │ +9F4C7 Flags 01 (1) 'Modification' │ │ │ │ +9F4C8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F4CC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F4CE Length 000B (11) │ │ │ │ +9F4D0 Version 01 (1) │ │ │ │ +9F4D1 UID Size 04 (4) │ │ │ │ +9F4D2 UID 00000000 (0) │ │ │ │ +9F4D6 GID Size 04 (4) │ │ │ │ +9F4D7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F4DB CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +9F4DF Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F4E0 Created OS 03 (3) 'Unix' │ │ │ │ +9F4E1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F4E2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F4E3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F4E5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F4E7 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F4EB CRC C9471F9E (3376881566) │ │ │ │ +9F4EF Compressed Size 00005184 (20868) │ │ │ │ +9F4F3 Uncompressed Size 0001FB6C (129900) │ │ │ │ +9F4F7 Filename Length 0015 (21) │ │ │ │ +9F4F9 Extra Length 0018 (24) │ │ │ │ +9F4FB Comment Length 0000 (0) │ │ │ │ +9F4FD Disk Start 0000 (0) │ │ │ │ +9F4FF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F501 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F505 Local Header Offset 0007C12F (508207) │ │ │ │ +9F509 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F509: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F51E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F520 Length 0005 (5) │ │ │ │ +9F522 Flags 01 (1) 'Modification' │ │ │ │ +9F523 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F527 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F529 Length 000B (11) │ │ │ │ +9F52B Version 01 (1) │ │ │ │ +9F52C UID Size 04 (4) │ │ │ │ +9F52D UID 00000000 (0) │ │ │ │ +9F531 GID Size 04 (4) │ │ │ │ +9F532 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F536 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +9F53A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F53B Created OS 03 (3) 'Unix' │ │ │ │ +9F53C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F53D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F53E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F540 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F542 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F546 CRC C3CD950E (3285030158) │ │ │ │ +9F54A Compressed Size 00001B07 (6919) │ │ │ │ +9F54E Uncompressed Size 000081CF (33231) │ │ │ │ +9F552 Filename Length 0019 (25) │ │ │ │ +9F554 Extra Length 0018 (24) │ │ │ │ +9F556 Comment Length 0000 (0) │ │ │ │ +9F558 Disk Start 0000 (0) │ │ │ │ +9F55A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F55C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F560 Local Header Offset 00081302 (529154) │ │ │ │ +9F564 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F564: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F57D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F57F Length 0005 (5) │ │ │ │ +9F581 Flags 01 (1) 'Modification' │ │ │ │ +9F582 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F586 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F588 Length 000B (11) │ │ │ │ +9F58A Version 01 (1) │ │ │ │ +9F58B UID Size 04 (4) │ │ │ │ +9F58C UID 00000000 (0) │ │ │ │ +9F590 GID Size 04 (4) │ │ │ │ +9F591 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F595 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +9F599 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F59A Created OS 03 (3) 'Unix' │ │ │ │ +9F59B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F59C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F59D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F59F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F5A1 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F5A5 CRC 15039DB2 (352558514) │ │ │ │ +9F5A9 Compressed Size 00000D96 (3478) │ │ │ │ +9F5AD Uncompressed Size 00002E9F (11935) │ │ │ │ +9F5B1 Filename Length 0018 (24) │ │ │ │ +9F5B3 Extra Length 0018 (24) │ │ │ │ +9F5B5 Comment Length 0000 (0) │ │ │ │ +9F5B7 Disk Start 0000 (0) │ │ │ │ +9F5B9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F5BB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F5BF Local Header Offset 00082E5C (536156) │ │ │ │ +9F5C3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F5C3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F5DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F5DD Length 0005 (5) │ │ │ │ +9F5DF Flags 01 (1) 'Modification' │ │ │ │ +9F5E0 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F5E4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F5E6 Length 000B (11) │ │ │ │ +9F5E8 Version 01 (1) │ │ │ │ +9F5E9 UID Size 04 (4) │ │ │ │ +9F5EA UID 00000000 (0) │ │ │ │ +9F5EE GID Size 04 (4) │ │ │ │ +9F5EF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F5F3 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +9F5F7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F5F8 Created OS 03 (3) 'Unix' │ │ │ │ +9F5F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F5FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F5FB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F5FD Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F5FF Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F603 CRC B429472D (3022604077) │ │ │ │ +9F607 Compressed Size 000001E1 (481) │ │ │ │ +9F60B Uncompressed Size 00000323 (803) │ │ │ │ +9F60F Filename Length 0011 (17) │ │ │ │ +9F611 Extra Length 0018 (24) │ │ │ │ +9F613 Comment Length 0000 (0) │ │ │ │ +9F615 Disk Start 0000 (0) │ │ │ │ +9F617 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F619 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F61D Local Header Offset 00083C44 (539716) │ │ │ │ +9F621 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F621: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F632 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F634 Length 0005 (5) │ │ │ │ +9F636 Flags 01 (1) 'Modification' │ │ │ │ +9F637 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F63B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F63D Length 000B (11) │ │ │ │ +9F63F Version 01 (1) │ │ │ │ +9F640 UID Size 04 (4) │ │ │ │ +9F641 UID 00000000 (0) │ │ │ │ +9F645 GID Size 04 (4) │ │ │ │ +9F646 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F64A CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +9F64E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F64F Created OS 03 (3) 'Unix' │ │ │ │ +9F650 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F651 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F652 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F654 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F656 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F65A CRC 1E45F0E3 (507900131) │ │ │ │ +9F65E Compressed Size 000006C1 (1729) │ │ │ │ +9F662 Uncompressed Size 00001439 (5177) │ │ │ │ +9F666 Filename Length 0019 (25) │ │ │ │ +9F668 Extra Length 0018 (24) │ │ │ │ +9F66A Comment Length 0000 (0) │ │ │ │ +9F66C Disk Start 0000 (0) │ │ │ │ +9F66E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F670 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F674 Local Header Offset 00083E70 (540272) │ │ │ │ +9F678 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F678: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F691 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F693 Length 0005 (5) │ │ │ │ +9F695 Flags 01 (1) 'Modification' │ │ │ │ +9F696 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F69A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F69C Length 000B (11) │ │ │ │ +9F69E Version 01 (1) │ │ │ │ +9F69F UID Size 04 (4) │ │ │ │ +9F6A0 UID 00000000 (0) │ │ │ │ +9F6A4 GID Size 04 (4) │ │ │ │ +9F6A5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F6A9 CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +9F6AD Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F6AE Created OS 03 (3) 'Unix' │ │ │ │ +9F6AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F6B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F6B1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F6B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F6B5 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F6B9 CRC 4E3CD307 (1312609031) │ │ │ │ +9F6BD Compressed Size 00001B8A (7050) │ │ │ │ +9F6C1 Uncompressed Size 00009F03 (40707) │ │ │ │ +9F6C5 Filename Length 0018 (24) │ │ │ │ +9F6C7 Extra Length 0018 (24) │ │ │ │ +9F6C9 Comment Length 0000 (0) │ │ │ │ +9F6CB Disk Start 0000 (0) │ │ │ │ +9F6CD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F6CF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F6D3 Local Header Offset 00084584 (542084) │ │ │ │ +9F6D7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F6D7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F6EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F6F1 Length 0005 (5) │ │ │ │ +9F6F3 Flags 01 (1) 'Modification' │ │ │ │ +9F6F4 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F6F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F6FA Length 000B (11) │ │ │ │ +9F6FC Version 01 (1) │ │ │ │ +9F6FD UID Size 04 (4) │ │ │ │ +9F6FE UID 00000000 (0) │ │ │ │ +9F702 GID Size 04 (4) │ │ │ │ +9F703 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F707 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +9F70B Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F70C Created OS 03 (3) 'Unix' │ │ │ │ +9F70D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F70E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F70F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F711 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F713 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F717 CRC 2C0CA940 (739027264) │ │ │ │ +9F71B Compressed Size 000016F8 (5880) │ │ │ │ +9F71F Uncompressed Size 00008AB6 (35510) │ │ │ │ +9F723 Filename Length 0012 (18) │ │ │ │ +9F725 Extra Length 0018 (24) │ │ │ │ +9F727 Comment Length 0000 (0) │ │ │ │ +9F729 Disk Start 0000 (0) │ │ │ │ +9F72B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F72D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F731 Local Header Offset 00086160 (549216) │ │ │ │ +9F735 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F735: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F747 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F749 Length 0005 (5) │ │ │ │ +9F74B Flags 01 (1) 'Modification' │ │ │ │ +9F74C Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F750 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F752 Length 000B (11) │ │ │ │ +9F754 Version 01 (1) │ │ │ │ +9F755 UID Size 04 (4) │ │ │ │ +9F756 UID 00000000 (0) │ │ │ │ +9F75A GID Size 04 (4) │ │ │ │ +9F75B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F75F CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +9F763 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F764 Created OS 03 (3) 'Unix' │ │ │ │ +9F765 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F766 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F767 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F769 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F76B Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F76F CRC 38930BD1 (949160913) │ │ │ │ +9F773 Compressed Size 00001E14 (7700) │ │ │ │ +9F777 Uncompressed Size 00008803 (34819) │ │ │ │ +9F77B Filename Length 0016 (22) │ │ │ │ +9F77D Extra Length 0018 (24) │ │ │ │ +9F77F Comment Length 0000 (0) │ │ │ │ +9F781 Disk Start 0000 (0) │ │ │ │ +9F783 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F785 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F789 Local Header Offset 000878A4 (555172) │ │ │ │ +9F78D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F78D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F7A3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F7A5 Length 0005 (5) │ │ │ │ +9F7A7 Flags 01 (1) 'Modification' │ │ │ │ +9F7A8 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F7AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F7AE Length 000B (11) │ │ │ │ +9F7B0 Version 01 (1) │ │ │ │ +9F7B1 UID Size 04 (4) │ │ │ │ +9F7B2 UID 00000000 (0) │ │ │ │ +9F7B6 GID Size 04 (4) │ │ │ │ +9F7B7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F7BB CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +9F7BF Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F7C0 Created OS 03 (3) 'Unix' │ │ │ │ +9F7C1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F7C2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F7C3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F7C5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F7C7 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F7CB CRC 85636E77 (2237886071) │ │ │ │ +9F7CF Compressed Size 000029AB (10667) │ │ │ │ +9F7D3 Uncompressed Size 0000D04F (53327) │ │ │ │ +9F7D7 Filename Length 001A (26) │ │ │ │ +9F7D9 Extra Length 0018 (24) │ │ │ │ +9F7DB Comment Length 0000 (0) │ │ │ │ +9F7DD Disk Start 0000 (0) │ │ │ │ +9F7DF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F7E1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F7E5 Local Header Offset 00089708 (562952) │ │ │ │ +9F7E9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F7E9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F803 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F805 Length 0005 (5) │ │ │ │ +9F807 Flags 01 (1) 'Modification' │ │ │ │ +9F808 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F80C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F80E Length 000B (11) │ │ │ │ +9F810 Version 01 (1) │ │ │ │ +9F811 UID Size 04 (4) │ │ │ │ +9F812 UID 00000000 (0) │ │ │ │ +9F816 GID Size 04 (4) │ │ │ │ +9F817 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F81B CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +9F81F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F820 Created OS 03 (3) 'Unix' │ │ │ │ +9F821 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F822 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F823 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F825 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F827 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F82B CRC 548FBB56 (1418705750) │ │ │ │ +9F82F Compressed Size 000009AC (2476) │ │ │ │ +9F833 Uncompressed Size 00001DB6 (7606) │ │ │ │ +9F837 Filename Length 0018 (24) │ │ │ │ +9F839 Extra Length 0018 (24) │ │ │ │ +9F83B Comment Length 0000 (0) │ │ │ │ +9F83D Disk Start 0000 (0) │ │ │ │ +9F83F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F841 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F845 Local Header Offset 0008C107 (573703) │ │ │ │ +9F849 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F849: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F861 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F863 Length 0005 (5) │ │ │ │ +9F865 Flags 01 (1) 'Modification' │ │ │ │ +9F866 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F86A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F86C Length 000B (11) │ │ │ │ +9F86E Version 01 (1) │ │ │ │ +9F86F UID Size 04 (4) │ │ │ │ +9F870 UID 00000000 (0) │ │ │ │ +9F874 GID Size 04 (4) │ │ │ │ +9F875 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F879 CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +9F87D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F87E Created OS 03 (3) 'Unix' │ │ │ │ +9F87F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F880 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F881 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F883 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F885 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F889 CRC F5E2129F (4125233823) │ │ │ │ +9F88D Compressed Size 000016BC (5820) │ │ │ │ +9F891 Uncompressed Size 000016CD (5837) │ │ │ │ +9F895 Filename Length 0015 (21) │ │ │ │ +9F897 Extra Length 0018 (24) │ │ │ │ +9F899 Comment Length 0000 (0) │ │ │ │ +9F89B Disk Start 0000 (0) │ │ │ │ +9F89D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F89F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F8A3 Local Header Offset 0008CB05 (576261) │ │ │ │ +9F8A7 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F8A7: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F8BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F8BE Length 0005 (5) │ │ │ │ +9F8C0 Flags 01 (1) 'Modification' │ │ │ │ +9F8C1 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F8C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F8C7 Length 000B (11) │ │ │ │ +9F8C9 Version 01 (1) │ │ │ │ +9F8CA UID Size 04 (4) │ │ │ │ +9F8CB UID 00000000 (0) │ │ │ │ +9F8CF GID Size 04 (4) │ │ │ │ +9F8D0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F8D4 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +9F8D8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F8D9 Created OS 03 (3) 'Unix' │ │ │ │ +9F8DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F8DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F8DC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F8DE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F8E0 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F8E4 CRC F5E2129F (4125233823) │ │ │ │ +9F8E8 Compressed Size 000016BC (5820) │ │ │ │ +9F8EC Uncompressed Size 000016CD (5837) │ │ │ │ +9F8F0 Filename Length 001C (28) │ │ │ │ +9F8F2 Extra Length 0018 (24) │ │ │ │ +9F8F4 Comment Length 0000 (0) │ │ │ │ +9F8F6 Disk Start 0000 (0) │ │ │ │ +9F8F8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F8FA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F8FE Local Header Offset 0008E210 (582160) │ │ │ │ +9F902 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F902: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F91E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F920 Length 0005 (5) │ │ │ │ +9F922 Flags 01 (1) 'Modification' │ │ │ │ +9F923 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F927 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F929 Length 000B (11) │ │ │ │ +9F92B Version 01 (1) │ │ │ │ +9F92C UID Size 04 (4) │ │ │ │ +9F92D UID 00000000 (0) │ │ │ │ +9F931 GID Size 04 (4) │ │ │ │ +9F932 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F936 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +9F93A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F93B Created OS 03 (3) 'Unix' │ │ │ │ +9F93C Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9F93D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F93E General Purpose Flag 0000 (0) │ │ │ │ +9F940 Compression Method 0000 (0) 'Stored' │ │ │ │ +9F942 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F946 CRC FC95F24B (4237685323) │ │ │ │ +9F94A Compressed Size 00001B84 (7044) │ │ │ │ +9F94E Uncompressed Size 00001B84 (7044) │ │ │ │ +9F952 Filename Length 0016 (22) │ │ │ │ +9F954 Extra Length 0018 (24) │ │ │ │ +9F956 Comment Length 0000 (0) │ │ │ │ +9F958 Disk Start 0000 (0) │ │ │ │ +9F95A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F95C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F960 Local Header Offset 0008F922 (588066) │ │ │ │ +9F964 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F964: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F97A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F97C Length 0005 (5) │ │ │ │ +9F97E Flags 01 (1) 'Modification' │ │ │ │ +9F97F Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F983 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F985 Length 000B (11) │ │ │ │ +9F987 Version 01 (1) │ │ │ │ +9F988 UID Size 04 (4) │ │ │ │ +9F989 UID 00000000 (0) │ │ │ │ +9F98D GID Size 04 (4) │ │ │ │ +9F98E GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F992 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +9F996 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F997 Created OS 03 (3) 'Unix' │ │ │ │ +9F998 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9F999 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F99A General Purpose Flag 0000 (0) │ │ │ │ +9F99C Compression Method 0000 (0) 'Stored' │ │ │ │ +9F99E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F9A2 CRC D0D71F86 (3503759238) │ │ │ │ +9F9A6 Compressed Size 00000B7B (2939) │ │ │ │ +9F9AA Uncompressed Size 00000B7B (2939) │ │ │ │ +9F9AE Filename Length 0016 (22) │ │ │ │ +9F9B0 Extra Length 0018 (24) │ │ │ │ +9F9B2 Comment Length 0000 (0) │ │ │ │ +9F9B4 Disk Start 0000 (0) │ │ │ │ +9F9B6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F9B8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F9BC Local Header Offset 000914F6 (595190) │ │ │ │ +9F9C0 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F9C0: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F9D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F9D8 Length 0005 (5) │ │ │ │ +9F9DA Flags 01 (1) 'Modification' │ │ │ │ +9F9DB Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9F9DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F9E1 Length 000B (11) │ │ │ │ +9F9E3 Version 01 (1) │ │ │ │ +9F9E4 UID Size 04 (4) │ │ │ │ +9F9E5 UID 00000000 (0) │ │ │ │ +9F9E9 GID Size 04 (4) │ │ │ │ +9F9EA GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F9EE CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +9F9F2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F9F3 Created OS 03 (3) 'Unix' │ │ │ │ +9F9F4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9F9F5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F9F6 General Purpose Flag 0000 (0) │ │ │ │ +9F9F8 Compression Method 0000 (0) 'Stored' │ │ │ │ +9F9FA Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9F9FE CRC FFF9C4D2 (4294558930) │ │ │ │ +9FA02 Compressed Size 0000138F (5007) │ │ │ │ +9FA06 Uncompressed Size 0000138F (5007) │ │ │ │ +9FA0A Filename Length 0016 (22) │ │ │ │ +9FA0C Extra Length 0018 (24) │ │ │ │ +9FA0E Comment Length 0000 (0) │ │ │ │ +9FA10 Disk Start 0000 (0) │ │ │ │ +9FA12 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FA14 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA18 Local Header Offset 000920C1 (598209) │ │ │ │ +9FA1C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA1C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FA32 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FA34 Length 0005 (5) │ │ │ │ +9FA36 Flags 01 (1) 'Modification' │ │ │ │ +9FA37 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FA3B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FA3D Length 000B (11) │ │ │ │ +9FA3F Version 01 (1) │ │ │ │ +9FA40 UID Size 04 (4) │ │ │ │ +9FA41 UID 00000000 (0) │ │ │ │ +9FA45 GID Size 04 (4) │ │ │ │ +9FA46 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FA4A CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +9FA4E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FA4F Created OS 03 (3) 'Unix' │ │ │ │ +9FA50 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9FA51 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FA52 General Purpose Flag 0000 (0) │ │ │ │ +9FA54 Compression Method 0000 (0) 'Stored' │ │ │ │ +9FA56 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FA5A CRC A1037E8E (2701360782) │ │ │ │ +9FA5E Compressed Size 0000145E (5214) │ │ │ │ +9FA62 Uncompressed Size 0000145E (5214) │ │ │ │ +9FA66 Filename Length 0016 (22) │ │ │ │ +9FA68 Extra Length 0018 (24) │ │ │ │ +9FA6A Comment Length 0000 (0) │ │ │ │ +9FA6C Disk Start 0000 (0) │ │ │ │ +9FA6E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FA70 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA74 Local Header Offset 000934A0 (603296) │ │ │ │ +9FA78 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA78: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FA8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FA90 Length 0005 (5) │ │ │ │ +9FA92 Flags 01 (1) 'Modification' │ │ │ │ +9FA93 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FA97 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FA99 Length 000B (11) │ │ │ │ +9FA9B Version 01 (1) │ │ │ │ +9FA9C UID Size 04 (4) │ │ │ │ +9FA9D UID 00000000 (0) │ │ │ │ +9FAA1 GID Size 04 (4) │ │ │ │ +9FAA2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FAA6 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +9FAAA Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FAAB Created OS 03 (3) 'Unix' │ │ │ │ +9FAAC Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9FAAD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FAAE General Purpose Flag 0000 (0) │ │ │ │ +9FAB0 Compression Method 0000 (0) 'Stored' │ │ │ │ +9FAB2 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FAB6 CRC 5E9E64F1 (1587438833) │ │ │ │ +9FABA Compressed Size 000008EC (2284) │ │ │ │ +9FABE Uncompressed Size 000008EC (2284) │ │ │ │ +9FAC2 Filename Length 0016 (22) │ │ │ │ +9FAC4 Extra Length 0018 (24) │ │ │ │ +9FAC6 Comment Length 0000 (0) │ │ │ │ +9FAC8 Disk Start 0000 (0) │ │ │ │ +9FACA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FACC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FAD0 Local Header Offset 0009494E (608590) │ │ │ │ +9FAD4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FAD4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FAEA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FAEC Length 0005 (5) │ │ │ │ +9FAEE Flags 01 (1) 'Modification' │ │ │ │ +9FAEF Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FAF3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FAF5 Length 000B (11) │ │ │ │ +9FAF7 Version 01 (1) │ │ │ │ +9FAF8 UID Size 04 (4) │ │ │ │ +9FAF9 UID 00000000 (0) │ │ │ │ +9FAFD GID Size 04 (4) │ │ │ │ +9FAFE GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FB02 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +9FB06 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FB07 Created OS 03 (3) 'Unix' │ │ │ │ +9FB08 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9FB09 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FB0A General Purpose Flag 0000 (0) │ │ │ │ +9FB0C Compression Method 0000 (0) 'Stored' │ │ │ │ +9FB0E Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FB12 CRC 42E340AB (1122189483) │ │ │ │ +9FB16 Compressed Size 00001F2E (7982) │ │ │ │ +9FB1A Uncompressed Size 00001F2E (7982) │ │ │ │ +9FB1E Filename Length 001E (30) │ │ │ │ +9FB20 Extra Length 0018 (24) │ │ │ │ +9FB22 Comment Length 0000 (0) │ │ │ │ +9FB24 Disk Start 0000 (0) │ │ │ │ +9FB26 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FB28 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FB2C Local Header Offset 0009528A (610954) │ │ │ │ +9FB30 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FB30: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FB4E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FB50 Length 0005 (5) │ │ │ │ +9FB52 Flags 01 (1) 'Modification' │ │ │ │ +9FB53 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FB57 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FB59 Length 000B (11) │ │ │ │ +9FB5B Version 01 (1) │ │ │ │ +9FB5C UID Size 04 (4) │ │ │ │ +9FB5D UID 00000000 (0) │ │ │ │ +9FB61 GID Size 04 (4) │ │ │ │ +9FB62 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FB66 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +9FB6A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FB6B Created OS 03 (3) 'Unix' │ │ │ │ +9FB6C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FB6D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FB6E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FB70 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FB72 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FB76 CRC DC7E4AC1 (3699264193) │ │ │ │ +9FB7A Compressed Size 00003D72 (15730) │ │ │ │ +9FB7E Uncompressed Size 00016649 (91721) │ │ │ │ +9FB82 Filename Length 001A (26) │ │ │ │ +9FB84 Extra Length 0018 (24) │ │ │ │ +9FB86 Comment Length 0000 (0) │ │ │ │ +9FB88 Disk Start 0000 (0) │ │ │ │ +9FB8A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FB8C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FB90 Local Header Offset 00097210 (619024) │ │ │ │ +9FB94 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FB94: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FBAE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FBB0 Length 0005 (5) │ │ │ │ +9FBB2 Flags 01 (1) 'Modification' │ │ │ │ +9FBB3 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FBB7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FBB9 Length 000B (11) │ │ │ │ +9FBBB Version 01 (1) │ │ │ │ +9FBBC UID Size 04 (4) │ │ │ │ +9FBBD UID 00000000 (0) │ │ │ │ +9FBC1 GID Size 04 (4) │ │ │ │ +9FBC2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FBC6 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +9FBCA Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FBCB Created OS 03 (3) 'Unix' │ │ │ │ +9FBCC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FBCD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FBCE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FBD0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FBD2 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FBD6 CRC 79CCCF16 (2043465494) │ │ │ │ +9FBDA Compressed Size 000029BA (10682) │ │ │ │ +9FBDE Uncompressed Size 0000BA6A (47722) │ │ │ │ +9FBE2 Filename Length 0018 (24) │ │ │ │ +9FBE4 Extra Length 0018 (24) │ │ │ │ +9FBE6 Comment Length 0000 (0) │ │ │ │ +9FBE8 Disk Start 0000 (0) │ │ │ │ +9FBEA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FBEC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FBF0 Local Header Offset 0009AFD6 (634838) │ │ │ │ +9FBF4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FBF4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FC0C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FC0E Length 0005 (5) │ │ │ │ +9FC10 Flags 01 (1) 'Modification' │ │ │ │ +9FC11 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FC15 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FC17 Length 000B (11) │ │ │ │ +9FC19 Version 01 (1) │ │ │ │ +9FC1A UID Size 04 (4) │ │ │ │ +9FC1B UID 00000000 (0) │ │ │ │ +9FC1F GID Size 04 (4) │ │ │ │ +9FC20 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FC24 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +9FC28 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FC29 Created OS 03 (3) 'Unix' │ │ │ │ +9FC2A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FC2B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FC2C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FC2E Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FC30 Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FC34 CRC DCB3B516 (3702764822) │ │ │ │ +9FC38 Compressed Size 000000AE (174) │ │ │ │ +9FC3C Uncompressed Size 000000FC (252) │ │ │ │ +9FC40 Filename Length 0016 (22) │ │ │ │ +9FC42 Extra Length 0018 (24) │ │ │ │ +9FC44 Comment Length 0000 (0) │ │ │ │ +9FC46 Disk Start 0000 (0) │ │ │ │ +9FC48 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FC4A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FC4E Local Header Offset 0009D9E2 (645602) │ │ │ │ +9FC52 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FC52: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FC68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FC6A Length 0005 (5) │ │ │ │ +9FC6C Flags 01 (1) 'Modification' │ │ │ │ +9FC6D Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FC71 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FC73 Length 000B (11) │ │ │ │ +9FC75 Version 01 (1) │ │ │ │ +9FC76 UID Size 04 (4) │ │ │ │ +9FC77 UID 00000000 (0) │ │ │ │ +9FC7B GID Size 04 (4) │ │ │ │ +9FC7C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FC80 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +9FC84 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FC85 Created OS 03 (3) 'Unix' │ │ │ │ +9FC86 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FC87 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FC88 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FC8A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FC8C Modification Time 5B620689 (1533150857) 'Sun Nov 2 00:52:18 2025' │ │ │ │ +9FC90 CRC 58439733 (1480824627) │ │ │ │ +9FC94 Compressed Size 00000077 (119) │ │ │ │ +9FC98 Uncompressed Size 000000A2 (162) │ │ │ │ +9FC9C Filename Length 002D (45) │ │ │ │ +9FC9E Extra Length 0018 (24) │ │ │ │ +9FCA0 Comment Length 0000 (0) │ │ │ │ +9FCA2 Disk Start 0000 (0) │ │ │ │ +9FCA4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FCA6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FCAA Local Header Offset 0009DAE0 (645856) │ │ │ │ +9FCAE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FCAE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FCDB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FCDD Length 0005 (5) │ │ │ │ +9FCDF Flags 01 (1) 'Modification' │ │ │ │ +9FCE0 Modification Time 6906AB43 (1762044739) 'Sun Nov 2 00:52:19 2025' │ │ │ │ +9FCE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FCE6 Length 000B (11) │ │ │ │ +9FCE8 Version 01 (1) │ │ │ │ +9FCE9 UID Size 04 (4) │ │ │ │ +9FCEA UID 00000000 (0) │ │ │ │ +9FCEE GID Size 04 (4) │ │ │ │ +9FCEF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FCF3 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +9FCF7 Number of this disk 0000 (0) │ │ │ │ +9FCF9 Central Dir Disk no 0000 (0) │ │ │ │ +9FCFB Entries in this disk 005B (91) │ │ │ │ +9FCFD Total Entries 005B (91) │ │ │ │ +9FCFF Size of Central Dir 00002135 (8501) │ │ │ │ +9FD03 Offset to Central Dir 0009DBBE (646078) │ │ │ │ +9FD07 Comment Length 0000 (0) │ │ │ │ # │ │ │ │ # Warning Count: 182 │ │ │ │ # │ │ │ │ # Done │ │ │ ├── filetype from file(1) │ │ │ │ @@ -1 +1 @@ │ │ │ │ -Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Oct 31 2025 13:18:12, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Nov 02 2025 00:52:18, uncompressed size 20, method=store │ │ │ ├── OEBPS/typespec.xhtml │ │ │ │ @@ -143,122 +143,122 @@ │ │ │ │ and optional (=>) association types. If an association type is mandatory, an │ │ │ │ association with that type needs to be present. In the case of an optional │ │ │ │ association type it is not required for the key type to be present.

The notation #{} specifies the singleton type for the empty map. Note that │ │ │ │ this notation is not a shorthand for the map/0 type.

For convenience, the following types are also built-in. They can be thought as │ │ │ │ predefined aliases for the type unions also shown in the table.

Built-in typeDefined as
term/0any/0
binary/0<<_:_*8>>
nonempty_binary/0<<_:8, _:_*8>>
bitstring/0<<_:_*1>>
nonempty_bitstring/0<<_:1, _:_*1>>
boolean/0'false' | 'true'
byte/00..255
char/00..16#10ffff
nil/0[]
number/0integer/0 | float/0
list/0[any()]
maybe_improper_list/0maybe_improper_list(any(), any())
nonempty_list/0nonempty_list(any())
string/0[char()]
nonempty_string/0[char(),...]
iodata/0iolist() | binary()
iolist/0maybe_improper_list(byte() | binary() | iolist(), binary() | [])
map/0#{any() => any()}
function/0fun()
module/0atom/0
mfa/0{module(),atom(),arity()}
arity/00..255
identifier/0pid() | port() | reference()
node/0atom/0
timeout/0'infinity' | non_neg_integer()
no_return/0none/0

Table: Built-in types, predefined aliases

In addition, the following three built-in types exist and can be thought as │ │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ │ according to the type language defined above.

Built-in typeCan be thought defined by the syntax
non_neg_integer/00..
pos_integer/01..
neg_integer/0..-1

Table: Additional built-in types

Note

The following built-in list types also exist, but they are expected to be │ │ │ │ -rarely used. Hence, they have long names:

nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
│ │ │ │ -nonempty_improper_list(Type1, Type2)
│ │ │ │ -nonempty_maybe_improper_list(Type1, Type2)

where the last two types define the set of Erlang terms one would expect.

Also for convenience, record notation is allowed to be used. Records are │ │ │ │ -shorthands for the corresponding tuples:

Record :: #Erlang_Atom{}
│ │ │ │ -        | #Erlang_Atom{Fields}

Records are extended to possibly contain type information. This is described in │ │ │ │ +rarely used. Hence, they have long names:

nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
│ │ │ │ +nonempty_improper_list(Type1, Type2)
│ │ │ │ +nonempty_maybe_improper_list(Type1, Type2)

where the last two types define the set of Erlang terms one would expect.

Also for convenience, record notation is allowed to be used. Records are │ │ │ │ +shorthands for the corresponding tuples:

Record :: #Erlang_Atom{}
│ │ │ │ +        | #Erlang_Atom{Fields}

Records are extended to possibly contain type information. This is described in │ │ │ │ Type Information in Record Declarations.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Redefining built-in types │ │ │ │

│ │ │ │

Change

Starting from Erlang/OTP 26, it is permitted to define a type having the same │ │ │ │ name as a built-in type.

It is recommended to avoid deliberately reusing built-in names because it can be │ │ │ │ confusing. However, when an Erlang/OTP release introduces a new type, code that │ │ │ │ happened to define its own type having the same name will continue to work.

As an example, imagine that the Erlang/OTP 42 release introduces a new type │ │ │ │ -gadget() defined like this:

-type gadget() :: {'gadget', reference()}.

Further imagine that some code has its own (different) definition of gadget(), │ │ │ │ -for example:

-type gadget() :: #{}.

Since redefinitions are allowed, the code will still compile (but with a │ │ │ │ +gadget() defined like this:

-type gadget() :: {'gadget', reference()}.

Further imagine that some code has its own (different) definition of gadget(), │ │ │ │ +for example:

-type gadget() :: #{}.

Since redefinitions are allowed, the code will still compile (but with a │ │ │ │ warning), and Dialyzer will not emit any additional warnings.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Declarations of User-Defined Types │ │ │ │

│ │ │ │

As seen, the basic syntax of a type is an atom followed by closed parentheses. │ │ │ │ New types are declared using -type and -opaque attributes as in the │ │ │ │ -following:

-type my_struct_type() :: Type.
│ │ │ │ --opaque my_opaq_type() :: Type.

The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ │ +following:

-type my_struct_type() :: Type.
│ │ │ │ +-opaque my_opaq_type() :: Type.

The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ │ type as defined in the previous section. A current restriction is that Type │ │ │ │ can contain only predefined types, or user-defined types which are either of the │ │ │ │ following:

For module-local types, the restriction that their definition exists in the │ │ │ │ module is enforced by the compiler and results in a compilation error. (A │ │ │ │ similar restriction currently exists for records.)

Type declarations can also be parameterized by including type variables between │ │ │ │ the parentheses. The syntax of type variables is the same as Erlang variables, │ │ │ │ that is, starts with an upper-case letter. These variables is to │ │ │ │ -appear on the RHS of the definition. A concrete example follows:

-type orddict(Key, Val) :: [{Key, Val}].

A module can export some types to declare that other modules are allowed to │ │ │ │ -refer to them as remote types. This declaration has the following form:

-export_type([T1/A1, ..., Tk/Ak]).

Here the Tis are atoms (the name of the type) and the Ais are their arguments.

Example:

-export_type([my_struct_type/0, orddict/2]).

Assuming that these types are exported from module 'mod', you can refer to │ │ │ │ -them from other modules using remote type expressions like the following:

mod:my_struct_type()
│ │ │ │ -mod:orddict(atom(), term())

It is not allowed to refer to types that are not declared as exported.

Types declared as opaque represent sets of terms whose structure is not │ │ │ │ +appear on the RHS of the definition. A concrete example follows:

-type orddict(Key, Val) :: [{Key, Val}].

A module can export some types to declare that other modules are allowed to │ │ │ │ +refer to them as remote types. This declaration has the following form:

-export_type([T1/A1, ..., Tk/Ak]).

Here the Tis are atoms (the name of the type) and the Ais are their arguments.

Example:

-export_type([my_struct_type/0, orddict/2]).

Assuming that these types are exported from module 'mod', you can refer to │ │ │ │ +them from other modules using remote type expressions like the following:

mod:my_struct_type()
│ │ │ │ +mod:orddict(atom(), term())

It is not allowed to refer to types that are not declared as exported.

Types declared as opaque represent sets of terms whose structure is not │ │ │ │ supposed to be visible from outside of their defining module. That is, only the │ │ │ │ module defining them is allowed to depend on their term structure. Consequently, │ │ │ │ such types do not make much sense as module local - module local types are not │ │ │ │ accessible by other modules anyway - and is always to be exported.

Read more on Opaques

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Information in Record Declarations │ │ │ │

│ │ │ │

The types of record fields can be specified in the declaration of the record. │ │ │ │ -The syntax for this is as follows:

-record(rec, {field1 :: Type1, field2, field3 :: Type3}).

For fields without type annotations, their type defaults to any(). That is, the │ │ │ │ -previous example is a shorthand for the following:

-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

In the presence of initial values for fields, the type must be declared after │ │ │ │ -the initialization, as follows:

-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

The initial values for fields are to be compatible with (that is, a member of) │ │ │ │ +The syntax for this is as follows:

-record(rec, {field1 :: Type1, field2, field3 :: Type3}).

For fields without type annotations, their type defaults to any(). That is, the │ │ │ │ +previous example is a shorthand for the following:

-record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

In the presence of initial values for fields, the type must be declared after │ │ │ │ +the initialization, as follows:

-record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

The initial values for fields are to be compatible with (that is, a member of) │ │ │ │ the corresponding types. This is checked by the compiler and results in a │ │ │ │ compilation error if a violation is detected.

Change

Before Erlang/OTP 19, for fields without initial values, the singleton type │ │ │ │ 'undefined' was added to all declared types. In other words, the following │ │ │ │ -two record declarations had identical effects:

-record(rec, {f1 = 42 :: integer(),
│ │ │ │ -             f2      :: float(),
│ │ │ │ -             f3      :: 'a' | 'b'}).
│ │ │ │ +two record declarations had identical effects:

-record(rec, {f1 = 42 :: integer(),
│ │ │ │ +             f2      :: float(),
│ │ │ │ +             f3      :: 'a' | 'b'}).
│ │ │ │  
│ │ │ │ --record(rec, {f1 = 42 :: integer(),
│ │ │ │ -              f2      :: 'undefined' | float(),
│ │ │ │ -              f3      :: 'undefined' | 'a' | 'b'}).

This is no longer the case. If you require 'undefined' in your record field │ │ │ │ +-record(rec, {f1 = 42 :: integer(), │ │ │ │ + f2 :: 'undefined' | float(), │ │ │ │ + f3 :: 'undefined' | 'a' | 'b'}).

This is no longer the case. If you require 'undefined' in your record field │ │ │ │ type, you must explicitly add it to the typespec, as in the 2nd example.

Any record, containing type information or not, once defined, can be used as a │ │ │ │ type using the following syntax:

#rec{}

In addition, the record fields can be further specified when using a record type │ │ │ │ by adding type information about the field as follows:

#rec{some_field :: Type}

Any unspecified fields are assumed to have the type in the original record │ │ │ │ declaration.

Note

When records are used to create patterns for ETS and Mnesia match functions, │ │ │ │ -Dialyzer may need some help not to emit bad warnings. For example:

-type height() :: pos_integer().
│ │ │ │ --record(person, {name :: string(), height :: height()}).
│ │ │ │ +Dialyzer may need some help not to emit bad warnings. For example:

-type height() :: pos_integer().
│ │ │ │ +-record(person, {name :: string(), height :: height()}).
│ │ │ │  
│ │ │ │ -lookup(Name, Tab) ->
│ │ │ │ -    ets:match_object(Tab, #person{name = Name, _ = '_'}).

Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ │ +lookup(Name, Tab) -> │ │ │ │ + ets:match_object(Tab, #person{name = Name, _ = '_'}).

Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ │ height.

The recommended way of dealing with this is to declare the smallest record │ │ │ │ field types to accommodate all your needs, and then create refinements as │ │ │ │ -needed. The modified example:

-record(person, {name :: string(), height :: height() | '_'}).
│ │ │ │ +needed. The modified example:

-record(person, {name :: string(), height :: height() | '_'}).
│ │ │ │  
│ │ │ │ --type person() :: #person{height :: height()}.

In specifications and type declarations the type person() is to be preferred │ │ │ │ +-type person() :: #person{height :: height()}.

In specifications and type declarations the type person() is to be preferred │ │ │ │ before #person{}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Specifications for Functions │ │ │ │

│ │ │ │

A specification (or contract) for a function is given using the -spec │ │ │ │ attribute. The general format is as follows:

-spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

An implementation of the function with the same name Function must exist in │ │ │ │ the current module, and the arity of the function must match the number of │ │ │ │ arguments, otherwise the compilation fails.

The following longer format with module name is also valid as long as Module │ │ │ │ is the name of the current module. This can be useful for documentation │ │ │ │ purposes.

-spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

Also, for documentation purposes, argument names can be given:

-spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

A function specification can be overloaded. That is, it can have several types, │ │ │ │ -separated by a semicolon (;). For example:

-spec foo(T1, T2) -> T3;
│ │ │ │ -         (T4, T5) -> T6.

A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ │ +separated by a semicolon (;). For example:

-spec foo(T1, T2) -> T3;
│ │ │ │ +         (T4, T5) -> T6.

A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ │ the domains of the argument types cannot overlap. For example, the following │ │ │ │ -specification results in a warning:

-spec foo(pos_integer()) -> pos_integer();
│ │ │ │ -         (integer()) -> integer().

Type variables can be used in specifications to specify relations for the input │ │ │ │ +specification results in a warning:

-spec foo(pos_integer()) -> pos_integer();
│ │ │ │ +         (integer()) -> integer().

Type variables can be used in specifications to specify relations for the input │ │ │ │ and output arguments of a function. For example, the following specification │ │ │ │ defines the type of a polymorphic identity function:

-spec id(X) -> X.

Notice that the above specification does not restrict the input and output type │ │ │ │ in any way. These types can be constrained by guard-like subtype constraints and │ │ │ │ -provide bounded quantification:

-spec id(X) -> X when X :: tuple().

Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ │ +provide bounded quantification:

-spec id(X) -> X when X :: tuple().

Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ │ constraint that can be used in the when part of a -spec attribute.

Note

The above function specification uses multiple occurrences of the same type │ │ │ │ variable. That provides more type information than the following function │ │ │ │ -specification, where the type variables are missing:

-spec id(tuple()) -> tuple().

The latter specification says that the function takes some tuple and returns │ │ │ │ +specification, where the type variables are missing:

-spec id(tuple()) -> tuple().

The latter specification says that the function takes some tuple and returns │ │ │ │ some tuple. The specification with the X type variable specifies that the │ │ │ │ function takes a tuple and returns the same tuple.

However, it is up to the tools that process the specifications to choose │ │ │ │ whether to take this extra information into account or not.

The scope of a :: constraint is the (...) -> RetType specification after │ │ │ │ which it appears. To avoid confusion, it is suggested that different variables │ │ │ │ are used in different constituents of an overloaded contract, as shown in the │ │ │ │ -following example:

-spec foo({X, integer()}) -> X when X :: atom();
│ │ │ │ -         ([Y]) -> Y when Y :: number().

Some functions in Erlang are not meant to return; either because they define │ │ │ │ +following example:

-spec foo({X, integer()}) -> X when X :: atom();
│ │ │ │ +         ([Y]) -> Y when Y :: number().

Some functions in Erlang are not meant to return; either because they define │ │ │ │ servers or because they are used to throw exceptions, as in the following │ │ │ │ -function:

my_error(Err) -> throw({error, Err}).

For such functions, it is recommended to use the special no_return/0 type │ │ │ │ +function:

my_error(Err) -> throw({error, Err}).

For such functions, it is recommended to use the special no_return/0 type │ │ │ │ for their "return", through a contract of the following form:

-spec my_error(term()) -> no_return().

Note

Erlang uses the shorthand version _ as an anonymous type variable equivalent │ │ │ │ to term/0 or any/0. For example, the following function

-spec Function(string(), _) -> string().

is equivalent to:

-spec Function(string(), any()) -> string().
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/tablesdatabases.xhtml │ │ │ │ @@ -51,73 +51,73 @@ │ │ │ │ │ │ │ │ │ │ │ │ Deleting an Element │ │ │ │ │ │ │ │

The delete operation is considered successful if the element was not present │ │ │ │ in the table. Hence all attempts to check that the element is present in the │ │ │ │ Ets/Mnesia table before deletion are unnecessary. Here follows an example for │ │ │ │ -Ets tables:

DO

ets:delete(Tab, Key),

DO NOT

case ets:lookup(Tab, Key) of
│ │ │ │ -    [] ->
│ │ │ │ +Ets tables:

DO

ets:delete(Tab, Key),

DO NOT

case ets:lookup(Tab, Key) of
│ │ │ │ +    [] ->
│ │ │ │          ok;
│ │ │ │ -    [_|_] ->
│ │ │ │ -        ets:delete(Tab, Key)
│ │ │ │ +    [_|_] ->
│ │ │ │ +        ets:delete(Tab, Key)
│ │ │ │  end,

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fetching Data │ │ │ │

│ │ │ │

Do not fetch data that you already have.

Consider that you have a module that handles the abstract data type Person. │ │ │ │ You export the interface function print_person/1, which uses the internal │ │ │ │ functions print_name/1, print_age/1, and print_occupation/1.

Note

If the function print_name/1, and so on, had been interface functions, the │ │ │ │ situation would have been different, as you do not want the user of the │ │ │ │ interface to know about the internal data representation.

DO

%%% Interface function
│ │ │ │ -print_person(PersonId) ->
│ │ │ │ +print_person(PersonId) ->
│ │ │ │      %% Look up the person in the named table person,
│ │ │ │ -    case ets:lookup(person, PersonId) of
│ │ │ │ -        [Person] ->
│ │ │ │ -            print_name(Person),
│ │ │ │ -            print_age(Person),
│ │ │ │ -            print_occupation(Person);
│ │ │ │ -        [] ->
│ │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
│ │ │ │ +    case ets:lookup(person, PersonId) of
│ │ │ │ +        [Person] ->
│ │ │ │ +            print_name(Person),
│ │ │ │ +            print_age(Person),
│ │ │ │ +            print_occupation(Person);
│ │ │ │ +        [] ->
│ │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
│ │ │ │      end.
│ │ │ │  
│ │ │ │  %%% Internal functions
│ │ │ │ -print_name(Person) ->
│ │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
│ │ │ │ +print_name(Person) ->
│ │ │ │ +    io:format("No person ~p~n", [Person#person.name]).
│ │ │ │  
│ │ │ │ -print_age(Person) ->
│ │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
│ │ │ │ +print_age(Person) ->
│ │ │ │ +    io:format("No person ~p~n", [Person#person.age]).
│ │ │ │  
│ │ │ │ -print_occupation(Person) ->
│ │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

DO NOT

%%% Interface function
│ │ │ │ -print_person(PersonId) ->
│ │ │ │ +print_occupation(Person) ->
│ │ │ │ +    io:format("No person ~p~n", [Person#person.occupation]).

DO NOT

%%% Interface function
│ │ │ │ +print_person(PersonId) ->
│ │ │ │      %% Look up the person in the named table person,
│ │ │ │ -    case ets:lookup(person, PersonId) of
│ │ │ │ -        [Person] ->
│ │ │ │ -            print_name(PersonID),
│ │ │ │ -            print_age(PersonID),
│ │ │ │ -            print_occupation(PersonID);
│ │ │ │ -        [] ->
│ │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
│ │ │ │ +    case ets:lookup(person, PersonId) of
│ │ │ │ +        [Person] ->
│ │ │ │ +            print_name(PersonID),
│ │ │ │ +            print_age(PersonID),
│ │ │ │ +            print_occupation(PersonID);
│ │ │ │ +        [] ->
│ │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
│ │ │ │      end.
│ │ │ │  
│ │ │ │  %%% Internal functions
│ │ │ │ -print_name(PersonID) ->
│ │ │ │ -    [Person] = ets:lookup(person, PersonId),
│ │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
│ │ │ │ -
│ │ │ │ -print_age(PersonID) ->
│ │ │ │ -    [Person] = ets:lookup(person, PersonId),
│ │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
│ │ │ │ -
│ │ │ │ -print_occupation(PersonID) ->
│ │ │ │ -    [Person] = ets:lookup(person, PersonId),
│ │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

│ │ │ │ +print_name(PersonID) -> │ │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ │ + io:format("No person ~p~n", [Person#person.name]). │ │ │ │ + │ │ │ │ +print_age(PersonID) -> │ │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ │ + io:format("No person ~p~n", [Person#person.age]). │ │ │ │ + │ │ │ │ +print_occupation(PersonID) -> │ │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ │ + io:format("No person ~p~n", [Person#person.occupation]).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Non-Persistent Database Storage │ │ │ │

│ │ │ │

For non-persistent database storage, prefer Ets tables over Mnesia │ │ │ │ local_content tables. Even the Mnesia dirty_write operations carry a fixed │ │ │ │ @@ -131,38 +131,38 @@ │ │ │ │ │ │ │ │

Assuming an Ets table that uses idno as key and contains the following:

[#person{idno = 1, name = "Adam",  age = 31, occupation = "mailman"},
│ │ │ │   #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"},
│ │ │ │   #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"},
│ │ │ │   #person{idno = 4, name = "Carl",  age = 25, occupation = "mailman"}]

If you must return all data stored in the Ets table, you can use │ │ │ │ ets:tab2list/1. However, usually you are only interested in a subset of the │ │ │ │ information in which case ets:tab2list/1 is expensive. If you only want to │ │ │ │ -extract one field from each record, for example, the age of every person, then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │ +extract one field from each record, for example, the age of every person, then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │                            name='_',
│ │ │ │                            age='$1',
│ │ │ │ -                          occupation = '_'},
│ │ │ │ -                [],
│ │ │ │ -                ['$1']}]),

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ -lists:map(fun(X) -> X#person.age end, TabList),

If you are only interested in the age of all persons named "Bryan", then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │ +                          occupation = '_'},
│ │ │ │ +                [],
│ │ │ │ +                ['$1']}]),

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ +lists:map(fun(X) -> X#person.age end, TabList),

If you are only interested in the age of all persons named "Bryan", then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │                            name="Bryan",
│ │ │ │                            age='$1',
│ │ │ │ -                          occupation = '_'},
│ │ │ │ -                [],
│ │ │ │ -                ['$1']}])

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ -lists:foldl(fun(X, Acc) -> case X#person.name of
│ │ │ │ +                          occupation = '_'},
│ │ │ │ +                [],
│ │ │ │ +                ['$1']}])

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ +lists:foldl(fun(X, Acc) -> case X#person.name of
│ │ │ │                                  "Bryan" ->
│ │ │ │ -                                    [X#person.age|Acc];
│ │ │ │ +                                    [X#person.age|Acc];
│ │ │ │                                   _ ->
│ │ │ │                                       Acc
│ │ │ │                             end
│ │ │ │ -             end, [], TabList)

If you need all information stored in the Ets table about persons named "Bryan", │ │ │ │ -then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │ +             end, [], TabList)

If you need all information stored in the Ets table about persons named "Bryan", │ │ │ │ +then:

DO

ets:select(Tab, [{#person{idno='_',
│ │ │ │                            name="Bryan",
│ │ │ │                            age='_',
│ │ │ │ -                          occupation = '_'}, [], ['$_']}]),

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ -lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

│ │ │ │ + occupation = '_'}, [], ['$_']}]),

DO NOT

TabList = ets:tab2list(Tab),
│ │ │ │ +lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ordered_set Tables │ │ │ │

│ │ │ │

If the data in the table is to be accessed so that the order of the keys in the │ │ │ │ table is significant, the table type ordered_set can be used instead of the │ │ │ │ @@ -198,20 +198,20 @@ │ │ │ │ Clearly, the second table would have to be kept consistent with the master │ │ │ │ table. Mnesia can do this for you, but a home-brew index table can be very │ │ │ │ efficient compared to the overhead involved in using Mnesia.

An index table for the table in the previous examples would have to be a bag (as │ │ │ │ keys would appear more than once) and can have the following contents:

[#index_entry{name="Adam", idno=1},
│ │ │ │   #index_entry{name="Bryan", idno=2},
│ │ │ │   #index_entry{name="Bryan", idno=3},
│ │ │ │   #index_entry{name="Carl", idno=4}]

Given this index table, a lookup of the age fields for all persons named │ │ │ │ -"Bryan" can be done as follows:

MatchingIDs = ets:lookup(IndexTable,"Bryan"),
│ │ │ │ -lists:map(fun(#index_entry{idno = ID}) ->
│ │ │ │ -                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
│ │ │ │ +"Bryan" can be done as follows:

MatchingIDs = ets:lookup(IndexTable,"Bryan"),
│ │ │ │ +lists:map(fun(#index_entry{idno = ID}) ->
│ │ │ │ +                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
│ │ │ │                   Age
│ │ │ │            end,
│ │ │ │ -          MatchingIDs),

Notice that this code does not use ets:match/2, but instead uses the │ │ │ │ + MatchingIDs),

Notice that this code does not use ets:match/2, but instead uses the │ │ │ │ ets:lookup/2 call. The lists:map/2 call is only used to traverse the idnos │ │ │ │ matching the name "Bryan" in the table; thus the number of lookups in the master │ │ │ │ table is minimized.

Keeping an index table introduces some overhead when inserting records in the │ │ │ │ table. The number of operations gained from the table must therefore be compared │ │ │ │ against the number of operations inserting objects in the table. However, notice │ │ │ │ that the gain is significant when the key can be used to lookup elements.

│ │ │ │ │ │ │ │ @@ -226,47 +226,47 @@ │ │ │ │ Secondary Index │ │ │ │

│ │ │ │

If you frequently do lookups on a field that is not the key of the table, you │ │ │ │ lose performance using mnesia:select() or │ │ │ │ mnesia:match_object() as these function traverse │ │ │ │ the whole table. Instead, you can create a secondary index and use │ │ │ │ mnesia:index_read/3 to get faster access at the expense of using more │ │ │ │ -memory.

Example:

-record(person, {idno, name, age, occupation}).
│ │ │ │ +memory.

Example:

-record(person, {idno, name, age, occupation}).
│ │ │ │          ...
│ │ │ │ -{atomic, ok} =
│ │ │ │ -mnesia:create_table(person, [{index,[#person.age]},
│ │ │ │ -                              {attributes,
│ │ │ │ -                                    record_info(fields, person)}]),
│ │ │ │ -{atomic, ok} = mnesia:add_table_index(person, age),
│ │ │ │ +{atomic, ok} =
│ │ │ │ +mnesia:create_table(person, [{index,[#person.age]},
│ │ │ │ +                              {attributes,
│ │ │ │ +                                    record_info(fields, person)}]),
│ │ │ │ +{atomic, ok} = mnesia:add_table_index(person, age),
│ │ │ │  ...
│ │ │ │  
│ │ │ │  PersonsAge42 =
│ │ │ │ -     mnesia:dirty_index_read(person, 42, #person.age),

│ │ │ │ + mnesia:dirty_index_read(person, 42, #person.age),

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Transactions │ │ │ │

│ │ │ │

Using transactions is a way to guarantee that the distributed Mnesia database │ │ │ │ remains consistent, even when many different processes update it in parallel. │ │ │ │ However, if you have real-time requirements it is recommended to use dirtry │ │ │ │ operations instead of transactions. When using dirty operations, you lose the │ │ │ │ consistency guarantee; this is usually solved by only letting one process update │ │ │ │ the table. Other processes must send update requests to that process.

Example:

...
│ │ │ │  %% Using transaction
│ │ │ │  
│ │ │ │ -Fun = fun() ->
│ │ │ │ -          [mnesia:read({Table, Key}),
│ │ │ │ -           mnesia:read({Table2, Key2})]
│ │ │ │ +Fun = fun() ->
│ │ │ │ +          [mnesia:read({Table, Key}),
│ │ │ │ +           mnesia:read({Table2, Key2})]
│ │ │ │        end,
│ │ │ │  
│ │ │ │ -{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
│ │ │ │ +{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
│ │ │ │  ...
│ │ │ │  
│ │ │ │  %% Same thing using dirty operations
│ │ │ │  ...
│ │ │ │  
│ │ │ │ -Result1 = mnesia:dirty_read({Table, Key}),
│ │ │ │ -Result2 = mnesia:dirty_read({Table2, Key2}),
│ │ │ │ +Result1 = mnesia:dirty_read({Table, Key}), │ │ │ │ +Result2 = mnesia:dirty_read({Table2, Key2}), │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/sup_princ.xhtml │ │ │ │ @@ -33,48 +33,48 @@ │ │ │ │ the order specified by this list, and are terminated in the reverse order.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │

│ │ │ │

The callback module for a supervisor starting the server from │ │ │ │ -gen_server Behaviour can look as follows:

-module(ch_sup).
│ │ │ │ --behaviour(supervisor).
│ │ │ │ +gen_server Behaviour can look as follows:

-module(ch_sup).
│ │ │ │ +-behaviour(supervisor).
│ │ │ │  
│ │ │ │ --export([start_link/0]).
│ │ │ │ --export([init/1]).
│ │ │ │ +-export([start_link/0]).
│ │ │ │ +-export([init/1]).
│ │ │ │  
│ │ │ │ -start_link() ->
│ │ │ │ -    supervisor:start_link(ch_sup, []).
│ │ │ │ +start_link() ->
│ │ │ │ +    supervisor:start_link(ch_sup, []).
│ │ │ │  
│ │ │ │ -init(_Args) ->
│ │ │ │ -    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
│ │ │ │ -    ChildSpecs = [#{id => ch3,
│ │ │ │ -                    start => {ch3, start_link, []},
│ │ │ │ +init(_Args) ->
│ │ │ │ +    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
│ │ │ │ +    ChildSpecs = [#{id => ch3,
│ │ │ │ +                    start => {ch3, start_link, []},
│ │ │ │                      restart => permanent,
│ │ │ │                      shutdown => brutal_kill,
│ │ │ │                      type => worker,
│ │ │ │ -                    modules => [ch3]}],
│ │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

The SupFlags variable in the return value from init/1 represents the │ │ │ │ + modules => [ch3]}], │ │ │ │ + {ok, {SupFlags, ChildSpecs}}.

The SupFlags variable in the return value from init/1 represents the │ │ │ │ supervisor flags.

The ChildSpecs variable in the return value from init/1 is a list of │ │ │ │ child specifications.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Flags │ │ │ │

│ │ │ │ -

This is the type definition for the supervisor flags:

sup_flags() = #{strategy => strategy(),           % optional
│ │ │ │ -                intensity => non_neg_integer(),   % optional
│ │ │ │ -                period => pos_integer(),          % optional
│ │ │ │ -                auto_shutdown => auto_shutdown()} % optional
│ │ │ │ -    strategy() = one_for_all
│ │ │ │ +

This is the type definition for the supervisor flags:

sup_flags() = #{strategy => strategy(),           % optional
│ │ │ │ +                intensity => non_neg_integer(),   % optional
│ │ │ │ +                period => pos_integer(),          % optional
│ │ │ │ +                auto_shutdown => auto_shutdown()} % optional
│ │ │ │ +    strategy() = one_for_all
│ │ │ │                 | one_for_one
│ │ │ │                 | rest_for_one
│ │ │ │                 | simple_one_for_one
│ │ │ │ -    auto_shutdown() = never
│ │ │ │ +    auto_shutdown() = never
│ │ │ │                      | any_significant
│ │ │ │                      | all_significant

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -313,28 +313,28 @@ │ │ │ │ exhaust the Maximum Restart Intensity of the │ │ │ │ parent supervisor.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Child Specification │ │ │ │

│ │ │ │ -

The type definition for a child specification is as follows:

child_spec() = #{id => child_id(),             % mandatory
│ │ │ │ -                 start => mfargs(),            % mandatory
│ │ │ │ -                 restart => restart(),         % optional
│ │ │ │ -                 significant => significant(), % optional
│ │ │ │ -                 shutdown => shutdown(),       % optional
│ │ │ │ -                 type => worker(),             % optional
│ │ │ │ -                 modules => modules()}         % optional
│ │ │ │ -    child_id() = term()
│ │ │ │ -    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
│ │ │ │ -    modules() = [module()] | dynamic
│ │ │ │ -    restart() = permanent | transient | temporary
│ │ │ │ -    significant() = boolean()
│ │ │ │ -    shutdown() = brutal_kill | timeout()
│ │ │ │ -    worker() = worker | supervisor
  • id is used to identify the child specification internally by the supervisor.

    The id key is mandatory.

    Note that this identifier occasionally has been called "name". As far as │ │ │ │ +

    The type definition for a child specification is as follows:

    child_spec() = #{id => child_id(),             % mandatory
    │ │ │ │ +                 start => mfargs(),            % mandatory
    │ │ │ │ +                 restart => restart(),         % optional
    │ │ │ │ +                 significant => significant(), % optional
    │ │ │ │ +                 shutdown => shutdown(),       % optional
    │ │ │ │ +                 type => worker(),             % optional
    │ │ │ │ +                 modules => modules()}         % optional
    │ │ │ │ +    child_id() = term()
    │ │ │ │ +    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
    │ │ │ │ +    modules() = [module()] | dynamic
    │ │ │ │ +    restart() = permanent | transient | temporary
    │ │ │ │ +    significant() = boolean()
    │ │ │ │ +    shutdown() = brutal_kill | timeout()
    │ │ │ │ +    worker() = worker | supervisor
    • id is used to identify the child specification internally by the supervisor.

      The id key is mandatory.

      Note that this identifier occasionally has been called "name". As far as │ │ │ │ possible, the terms "identifier" or "id" are now used but in order to keep │ │ │ │ backwards compatibility, some occurrences of "name" can still be found, for │ │ │ │ example in error messages.

    • start defines the function call used to start the child process. It is a │ │ │ │ module-function-arguments tuple used as apply(M, F, A).

      It is to be (or result in) a call to any of the following:

      The start key is mandatory.

    • restart defines when a terminated child process is to be │ │ │ │ restarted.

      • A permanent child process is always restarted.
      • A temporary child process is never restarted (not even when the supervisor │ │ │ │ restart strategy is rest_for_one or one_for_all and a sibling death │ │ │ │ @@ -362,53 +362,53 @@ │ │ │ │ supervisor, the default value infinity will be used.

      • type specifies whether the child process is a supervisor or a worker.

        The type key is optional. If it is not given, the default value worker │ │ │ │ will be used.

      • modules has to be a list consisting of a single element. The value │ │ │ │ of that element depends on the behaviour of the process:

        • If the child process is a gen_event, the element has to be the atom │ │ │ │ dynamic.
        • Otherwise, the element should be Module, where Module is the │ │ │ │ name of the callback module.

        This information is used by the release handler during upgrades and │ │ │ │ downgrades; see Release Handling.

        The modules key is optional. If it is not given, it defaults to [M], where │ │ │ │ M comes from the child's start {M,F,A}.

      Example: The child specification to start the server ch3 in the previous │ │ │ │ -example look as follows:

      #{id => ch3,
      │ │ │ │ -  start => {ch3, start_link, []},
      │ │ │ │ +example look as follows:

      #{id => ch3,
      │ │ │ │ +  start => {ch3, start_link, []},
      │ │ │ │    restart => permanent,
      │ │ │ │    shutdown => brutal_kill,
      │ │ │ │    type => worker,
      │ │ │ │ -  modules => [ch3]}

      or simplified, relying on the default values:

      #{id => ch3,
      │ │ │ │ +  modules => [ch3]}

      or simplified, relying on the default values:

      #{id => ch3,
      │ │ │ │    start => {ch3, start_link, []},
      │ │ │ │    shutdown => brutal_kill}

      Example: A child specification to start the event manager from the chapter about │ │ │ │ -gen_event:

      #{id => error_man,
      │ │ │ │ -  start => {gen_event, start_link, [{local, error_man}]},
      │ │ │ │ -  modules => dynamic}

      Both server and event manager are registered processes which can be expected to │ │ │ │ +gen_event:

      #{id => error_man,
      │ │ │ │ +  start => {gen_event, start_link, [{local, error_man}]},
      │ │ │ │ +  modules => dynamic}

      Both server and event manager are registered processes which can be expected to │ │ │ │ be always accessible. Thus they are specified to be permanent.

      ch3 does not need to do any cleaning up before termination. Thus, no shutdown │ │ │ │ time is needed, but brutal_kill is sufficient. error_man can need some time │ │ │ │ for the event handlers to clean up, thus the shutdown time is set to 5000 ms │ │ │ │ -(which is the default value).

      Example: A child specification to start another supervisor:

      #{id => sup,
      │ │ │ │ -  start => {sup, start_link, []},
      │ │ │ │ +(which is the default value).

      Example: A child specification to start another supervisor:

      #{id => sup,
      │ │ │ │ +  start => {sup, start_link, []},
      │ │ │ │    restart => transient,
      │ │ │ │ -  type => supervisor} % will cause default shutdown=>infinity

      │ │ │ │ + type => supervisor} % will cause default shutdown=>infinity

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting a Supervisor │ │ │ │

      │ │ │ │

      In the previous example, the supervisor is started by calling │ │ │ │ -ch_sup:start_link():

      start_link() ->
      │ │ │ │ -    supervisor:start_link(ch_sup, []).

      ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ │ +ch_sup:start_link():

      start_link() ->
      │ │ │ │ +    supervisor:start_link(ch_sup, []).

      ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ │ links to a new process, a supervisor.

      • The first argument, ch_sup, is the name of the callback module, that is, the │ │ │ │ module where the init callback function is located.
      • The second argument, [], is a term that is passed as is to the callback │ │ │ │ function init. Here, init does not need any data and ignores the argument.

      In this case, the supervisor is not registered. Instead its pid must be used. A │ │ │ │ name can be specified by calling │ │ │ │ supervisor:start_link({local, Name}, Module, Args) │ │ │ │ or │ │ │ │ supervisor:start_link({global, Name}, Module, Args).

      The new supervisor process calls the callback function ch_sup:init([]). init │ │ │ │ -has to return {ok, {SupFlags, ChildSpecs}}:

      init(_Args) ->
      │ │ │ │ -    SupFlags = #{},
      │ │ │ │ -    ChildSpecs = [#{id => ch3,
      │ │ │ │ -                    start => {ch3, start_link, []},
      │ │ │ │ -                    shutdown => brutal_kill}],
      │ │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

      Subsequently, the supervisor starts its child processes according to the child │ │ │ │ +has to return {ok, {SupFlags, ChildSpecs}}:

      init(_Args) ->
      │ │ │ │ +    SupFlags = #{},
      │ │ │ │ +    ChildSpecs = [#{id => ch3,
      │ │ │ │ +                    start => {ch3, start_link, []},
      │ │ │ │ +                    shutdown => brutal_kill}],
      │ │ │ │ +    {ok, {SupFlags, ChildSpecs}}.

      Subsequently, the supervisor starts its child processes according to the child │ │ │ │ specifications in the start specification. In this case there is a single child │ │ │ │ process, called ch3.

      supervisor:start_link/3 is synchronous. It does not return until all child │ │ │ │ processes have been started.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding a Child Process │ │ │ │ @@ -437,31 +437,31 @@ │ │ │ │ │ │ │ │ │ │ │ │ Simplified one_for_one Supervisors │ │ │ │

      │ │ │ │

      A supervisor with restart strategy simple_one_for_one is a simplified │ │ │ │ one_for_one supervisor, where all child processes are dynamically added │ │ │ │ instances of the same process.

      The following is an example of a callback module for a simple_one_for_one │ │ │ │ -supervisor:

      -module(simple_sup).
      │ │ │ │ --behaviour(supervisor).
      │ │ │ │ +supervisor:

      -module(simple_sup).
      │ │ │ │ +-behaviour(supervisor).
      │ │ │ │  
      │ │ │ │ --export([start_link/0]).
      │ │ │ │ --export([init/1]).
      │ │ │ │ +-export([start_link/0]).
      │ │ │ │ +-export([init/1]).
      │ │ │ │  
      │ │ │ │ -start_link() ->
      │ │ │ │ -    supervisor:start_link(simple_sup, []).
      │ │ │ │ +start_link() ->
      │ │ │ │ +    supervisor:start_link(simple_sup, []).
      │ │ │ │  
      │ │ │ │ -init(_Args) ->
      │ │ │ │ -    SupFlags = #{strategy => simple_one_for_one,
      │ │ │ │ +init(_Args) ->
      │ │ │ │ +    SupFlags = #{strategy => simple_one_for_one,
      │ │ │ │                   intensity => 0,
      │ │ │ │ -                 period => 1},
      │ │ │ │ -    ChildSpecs = [#{id => call,
      │ │ │ │ -                    start => {call, start_link, []},
      │ │ │ │ -                    shutdown => brutal_kill}],
      │ │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

      When started, the supervisor does not start any child │ │ │ │ + period => 1}, │ │ │ │ + ChildSpecs = [#{id => call, │ │ │ │ + start => {call, start_link, []}, │ │ │ │ + shutdown => brutal_kill}], │ │ │ │ + {ok, {SupFlags, ChildSpecs}}.

      When started, the supervisor does not start any child │ │ │ │ processes. Instead, all child processes need to be added dynamically by │ │ │ │ calling supervisor:start_child(Sup, List).

      Sup is the pid, or name, of the supervisor. List is an arbitrary list of │ │ │ │ terms, which are added to the list of arguments specified in the child │ │ │ │ specification. If the start function is specified as {M, F, A}, the child │ │ │ │ process is started by calling apply(M, F, A++List).

      For example, adding a child to simple_sup above:

      supervisor:start_child(Pid, [id1])

      The result is that the child process is started by calling │ │ │ │ apply(call, start_link, []++[id1]), or actually:

      call:start_link(id1)

      A child under a simple_one_for_one supervisor can be terminated with the │ │ │ │ following:

      supervisor:terminate_child(Sup, Pid)

      Sup is the pid, or name, of the supervisor and Pid is the pid of the child.

      Because a simple_one_for_one supervisor can have many children, it shuts them │ │ │ ├── OEBPS/statem.xhtml │ │ │ │ @@ -29,15 +29,15 @@ │ │ │ │ │ │ │ │

      Established Automata Theory does not deal much with how a state transition │ │ │ │ is triggered, but assumes that the output is a function of the input │ │ │ │ (and the state) and that they are some kind of values.

      For an Event-Driven State Machine, the input is an event that triggers │ │ │ │ a state transition and the output is actions executed during │ │ │ │ the state transition. Analogously to the mathematical model │ │ │ │ of a Finite State Machine, it can be described as a set of relations │ │ │ │ -of the following form:

      State(S) x Event(E) -> Actions(A), State(S')

      These relations are interpreted as follows: if we are in state S, │ │ │ │ +of the following form:

      State(S) x Event(E) -> Actions(A), State(S')

      These relations are interpreted as follows: if we are in state S, │ │ │ │ and event E occurs, we are to perform actions A, and make a transition │ │ │ │ to state S'. Notice that S' can be equal to S, │ │ │ │ and that A can be empty.

      In gen_statem we define a state change as a state transition in which the │ │ │ │ new state S' is different from the current state S, where "different" means │ │ │ │ Erlang's strict inequality: =/= also known as "does not match". gen_statem │ │ │ │ does more things during state changes than during other state transitions.

      As A and S' depend only on S and E, the kind of state machine described │ │ │ │ here is a Mealy machine (see, for example, the Wikipedia article │ │ │ │ @@ -310,20 +310,20 @@ │ │ │ │ │ │ │ │ State Enter Calls │ │ │ │ │ │ │ │

      The gen_statem behaviour can, if this is enabled, regardless of callback │ │ │ │ mode, automatically call the state callback │ │ │ │ with special arguments whenever the state changes, so you can write │ │ │ │ state enter actions near the rest of the state transition rules. │ │ │ │ -It typically looks like this:

      StateName(enter, OldState, Data) ->
      │ │ │ │ +It typically looks like this:

      StateName(enter, OldState, Data) ->
      │ │ │ │      ... code for state enter actions here ...
      │ │ │ │ -    {keep_state, NewData};
      │ │ │ │ -StateName(EventType, EventContent, Data) ->
      │ │ │ │ +    {keep_state, NewData};
      │ │ │ │ +StateName(EventType, EventContent, Data) ->
      │ │ │ │      ... code for actions here ...
      │ │ │ │ -    {next_state, NewStateName, NewData}.

      Since the state enter call is not an event there are restrictions on the │ │ │ │ + {next_state, NewStateName, NewData}.

      Since the state enter call is not an event there are restrictions on the │ │ │ │ allowed return value and state transition actions. │ │ │ │ You must not change the state, postpone this non-event, │ │ │ │ insert any events, or change the │ │ │ │ callback module.

      The first state that is entered after gen_statem:init/1 will get │ │ │ │ a state enter call with OldState equal to the current state.

      You may repeat the state enter call using the {repeat_state,...} return │ │ │ │ value from the state callback. In this case │ │ │ │ OldState will also be equal to the current state.

      Depending on how your state machine is specified, this can be a very useful │ │ │ │ @@ -404,72 +404,72 @@ │ │ │ │ │ │ │ │ locked --> check_code : {button, Button}\n* Collect Buttons │ │ │ │ check_code --> locked : Incorrect code │ │ │ │ check_code --> open : Correct code\n* do_unlock()\n* Clear Buttons\n* Set state_timeout 10 s │ │ │ │ │ │ │ │ open --> open : {button, Digit} │ │ │ │ open --> locked : state_timeout\n* do_lock()

      This code lock state machine can be implemented using gen_statem with │ │ │ │ -the following callback module:

      -module(code_lock).
      │ │ │ │ --behaviour(gen_statem).
      │ │ │ │ --define(NAME, code_lock).
      │ │ │ │ +the following callback module:

      -module(code_lock).
      │ │ │ │ +-behaviour(gen_statem).
      │ │ │ │ +-define(NAME, code_lock).
      │ │ │ │  
      │ │ │ │ --export([start_link/1]).
      │ │ │ │ --export([button/1]).
      │ │ │ │ --export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ --export([locked/3,open/3]).
      │ │ │ │ -
      │ │ │ │ -start_link(Code) ->
      │ │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
      │ │ │ │ -
      │ │ │ │ -button(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
      │ │ │ │ -
      │ │ │ │ -init(Code) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ -    {ok, locked, Data}.
      │ │ │ │ -
      │ │ │ │ -callback_mode() ->
      │ │ │ │ -    state_functions.
      locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +-export([start_link/1]).
      │ │ │ │ +-export([button/1]).
      │ │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ +-export([locked/3,open/3]).
      │ │ │ │ +
      │ │ │ │ +start_link(Code) ->
      │ │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
      │ │ │ │ +
      │ │ │ │ +button(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
      │ │ │ │ +
      │ │ │ │ +init(Code) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ +    {ok, locked, Data}.
      │ │ │ │ +
      │ │ │ │ +callback_mode() ->
      │ │ │ │ +    state_functions.
      locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │      NewButtons =
      │ │ │ │          if
      │ │ │ │ -            length(Buttons) < Length ->
      │ │ │ │ +            length(Buttons) < Length ->
      │ │ │ │                  Buttons;
      │ │ │ │              true ->
      │ │ │ │ -                tl(Buttons)
      │ │ │ │ -        end ++ [Button],
      │ │ │ │ +                tl(Buttons)
      │ │ │ │ +        end ++ [Button],
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -	    do_unlock(),
      │ │ │ │ -            {next_state, open, Data#{buttons := []},
      │ │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +	    do_unlock(),
      │ │ │ │ +            {next_state, open, Data#{buttons := []},
      │ │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
      │ │ │ │ -    end.
      open(state_timeout, lock,  Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {next_state, locked, Data};
      │ │ │ │ -open(cast, {button,_}, Data) ->
      │ │ │ │ -    {next_state, open, Data}.
      do_lock() ->
      │ │ │ │ -    io:format("Lock~n", []).
      │ │ │ │ -do_unlock() ->
      │ │ │ │ -    io:format("Unlock~n", []).
      │ │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
      │ │ │ │ +    end.
      open(state_timeout, lock,  Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {next_state, locked, Data};
      │ │ │ │ +open(cast, {button,_}, Data) ->
      │ │ │ │ +    {next_state, open, Data}.
      do_lock() ->
      │ │ │ │ +    io:format("Lock~n", []).
      │ │ │ │ +do_unlock() ->
      │ │ │ │ +    io:format("Unlock~n", []).
      │ │ │ │  
      │ │ │ │ -terminate(_Reason, State, _Data) ->
      │ │ │ │ -    State =/= locked andalso do_lock(),
      │ │ │ │ +terminate(_Reason, State, _Data) ->
      │ │ │ │ +    State =/= locked andalso do_lock(),
      │ │ │ │      ok.

      The code is explained in the next sections.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting gen_statem │ │ │ │

      │ │ │ │

      In the example in the previous section, gen_statem is started by calling │ │ │ │ -code_lock:start_link(Code):

      start_link(Code) ->
      │ │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

      start_link/1 calls function gen_statem:start_link/4, │ │ │ │ +code_lock:start_link(Code):

      start_link(Code) ->
      │ │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

      start_link/1 calls function gen_statem:start_link/4, │ │ │ │ which spawns and links to a new process, a gen_statem.

      • The first argument, {local,?NAME}, specifies the name. In this case, the │ │ │ │ gen_statem is locally registered as code_lock through the macro ?NAME.

        If the name is omitted, the gen_statem is not registered. Instead its pid │ │ │ │ must be used. The name can also be specified as {global, Name}, then the │ │ │ │ gen_statem is registered using global:register_name/2 in Kernel.

      • The second argument, ?MODULE, is the name of the callback module, │ │ │ │ that is, the module where the callback functions are located, │ │ │ │ which is this module.

        The interface functions (start_link/1 and button/1) are located in the │ │ │ │ same module as the callback functions (init/1, locked/3, and open/3). │ │ │ │ @@ -479,184 +479,184 @@ │ │ │ │ see gen_statem:start_link/3.

      If name registration succeeds, the new gen_statem process calls callback │ │ │ │ function code_lock:init(Code). This function is expected to return │ │ │ │ {ok, State, Data}, where State is the initial state of the gen_statem, │ │ │ │ in this case locked; assuming that the door is locked to begin with. │ │ │ │ Data is the internal server data of the gen_statem. Here the server data │ │ │ │ is a map() with key code that stores the correct │ │ │ │ button sequence, key length store its length, and key buttons │ │ │ │ -that stores the collected buttons up to the same length.

      init(Code) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ -    {ok, locked, Data}.

      Function gen_statem:start_link/3,4 │ │ │ │ +that stores the collected buttons up to the same length.

      init(Code) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ +    {ok, locked, Data}.

      Function gen_statem:start_link/3,4 │ │ │ │ is synchronous. It does not return until the gen_statem is initialized │ │ │ │ and is ready to receive events.

      Function gen_statem:start_link/3,4 │ │ │ │ must be used if the gen_statem is part of a supervision tree, that is, │ │ │ │ started by a supervisor. Function, │ │ │ │ gen_statem:start/3,4 can be used to start │ │ │ │ a standalone gen_statem, meaning it is not part of a supervision tree.

      Function Module:callback_mode/0 selects │ │ │ │ the CallbackMode for the callback module, │ │ │ │ in this case state_functions. │ │ │ │ -That is, each state has its own handler function:

      callback_mode() ->
      │ │ │ │ +That is, each state has its own handler function:

      callback_mode() ->
      │ │ │ │      state_functions.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Events │ │ │ │

      │ │ │ │

      The function notifying the code lock about a button event is implemented using │ │ │ │ -gen_statem:cast/2:

      button(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {button,Button}).

      The first argument is the name of the gen_statem and must agree with │ │ │ │ +gen_statem:cast/2:

      button(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {button,Button}).

      The first argument is the name of the gen_statem and must agree with │ │ │ │ the name used to start it. So, we use the same macro ?NAME as when starting. │ │ │ │ {button, Button} is the event content.

      The event is sent to the gen_statem. When the event is received, the │ │ │ │ gen_statem calls StateName(cast, Event, Data), which is expected │ │ │ │ to return a tuple {next_state, NewStateName, NewData}, or │ │ │ │ {next_state, NewStateName, NewData, Actions}. StateName is the name │ │ │ │ of the current state and NewStateName is the name of the next state. │ │ │ │ NewData is a new value for the server data of the gen_statem, │ │ │ │ -and Actions is a list of actions to be performed by the gen_statem engine.

      locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +and Actions is a list of actions to be performed by the gen_statem engine.

      locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │      NewButtons =
      │ │ │ │          if
      │ │ │ │ -            length(Buttons) < Length ->
      │ │ │ │ +            length(Buttons) < Length ->
      │ │ │ │                  Buttons;
      │ │ │ │              true ->
      │ │ │ │ -                tl(Buttons)
      │ │ │ │ -        end ++ [Button],
      │ │ │ │ +                tl(Buttons)
      │ │ │ │ +        end ++ [Button],
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -	    do_unlock(),
      │ │ │ │ -            {next_state, open, Data#{buttons := []},
      │ │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +	    do_unlock(),
      │ │ │ │ +            {next_state, open, Data#{buttons := []},
      │ │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
      │ │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
      │ │ │ │      end.

      In state locked, when a button is pressed, it is collected with the │ │ │ │ previously pressed buttons up to the length of the correct code, then │ │ │ │ compared with the correct code. Depending on the result, the door is │ │ │ │ either unlocked and the gen_statem goes to state open, or the door │ │ │ │ remains in state locked.

      When changing to state open, the collected buttons are reset, the lock │ │ │ │ -unlocked, and a state time-out for 10 seconds is started.

      open(cast, {button,_}, Data) ->
      │ │ │ │ -    {next_state, open, Data}.

      In state open, a button event is ignored by staying in the same state. │ │ │ │ +unlocked, and a state time-out for 10 seconds is started.

      open(cast, {button,_}, Data) ->
      │ │ │ │ +    {next_state, open, Data}.

      In state open, a button event is ignored by staying in the same state. │ │ │ │ This can also be done by returning {keep_state, Data}, or in this case │ │ │ │ since Data is unchanged, by returning keep_state_and_data.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ State Time-Outs │ │ │ │

      │ │ │ │

      When a correct code has been given, the door is unlocked and the following │ │ │ │ -tuple is returned from locked/2:

      {next_state, open, Data#{buttons := []},
      │ │ │ │ - [{state_timeout,10_000,lock}]}; % Time in milliseconds

      10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ │ +tuple is returned from locked/2:

      {next_state, open, Data#{buttons := []},
      │ │ │ │ + [{state_timeout,10_000,lock}]}; % Time in milliseconds

      10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ │ a time-out occurs. Then, StateName(state_timeout, lock, Data) is called. │ │ │ │ The time-out occurs when the door has been in state open for 10 seconds. │ │ │ │ -After that the door is locked again:

      open(state_timeout, lock,  Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {next_state, locked, Data};

      The timer for a state time-out is automatically canceled when │ │ │ │ +After that the door is locked again:

      open(state_timeout, lock,  Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {next_state, locked, Data};

      The timer for a state time-out is automatically canceled when │ │ │ │ the state machine does a state change.

      You can restart, cancel, or update a state time-out. See section │ │ │ │ Time-Outs for details.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ All State Events │ │ │ │

      │ │ │ │

      Sometimes events can arrive in any state of the gen_statem. It is convenient │ │ │ │ to handle these in a common state handler function that all state functions │ │ │ │ call for events not specific to the state.

      Consider a code_length/0 function that returns the length │ │ │ │ of the correct code. We dispatch all events that are not state-specific │ │ │ │ to the common function handle_common/3:

      ...
      │ │ │ │ --export([button/1,code_length/0]).
      │ │ │ │ +-export([button/1,code_length/0]).
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -code_length() ->
      │ │ │ │ -    gen_statem:call(?NAME, code_length).
      │ │ │ │ +code_length() ->
      │ │ │ │ +    gen_statem:call(?NAME, code_length).
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -locked(...) -> ... ;
      │ │ │ │ -locked(EventType, EventContent, Data) ->
      │ │ │ │ -    handle_common(EventType, EventContent, Data).
      │ │ │ │ +locked(...) -> ... ;
      │ │ │ │ +locked(EventType, EventContent, Data) ->
      │ │ │ │ +    handle_common(EventType, EventContent, Data).
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -open(...) -> ... ;
      │ │ │ │ -open(EventType, EventContent, Data) ->
      │ │ │ │ -    handle_common(EventType, EventContent, Data).
      │ │ │ │ -
      │ │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
      │ │ │ │ -    {keep_state, Data,
      │ │ │ │ -     [{reply,From,length(Code)}]}.

      Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

      ...
      │ │ │ │ --export([button/1,code_length/0]).
      │ │ │ │ +open(...) -> ... ;
      │ │ │ │ +open(EventType, EventContent, Data) ->
      │ │ │ │ +    handle_common(EventType, EventContent, Data).
      │ │ │ │ +
      │ │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
      │ │ │ │ +    {keep_state, Data,
      │ │ │ │ +     [{reply,From,length(Code)}]}.

      Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

      ...
      │ │ │ │ +-export([button/1,code_length/0]).
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -code_length() ->
      │ │ │ │ -    gen_statem:call(?NAME, code_length).
      │ │ │ │ +code_length() ->
      │ │ │ │ +    gen_statem:call(?NAME, code_length).
      │ │ │ │  
      │ │ │ │ --define(HANDLE_COMMON,
      │ │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
      │ │ │ │ +-define(HANDLE_COMMON,
      │ │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
      │ │ │ │  %%
      │ │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
      │ │ │ │ -    {keep_state, Data,
      │ │ │ │ -     [{reply,From,length(Code)}]}.
      │ │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
      │ │ │ │ +    {keep_state, Data,
      │ │ │ │ +     [{reply,From,length(Code)}]}.
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -locked(...) -> ... ;
      │ │ │ │ +locked(...) -> ... ;
      │ │ │ │  ?HANDLE_COMMON.
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -open(...) -> ... ;
      │ │ │ │ +open(...) -> ... ;
      │ │ │ │  ?HANDLE_COMMON.

      This example uses gen_statem:call/2, which waits for a reply from the server. │ │ │ │ The reply is sent with a {reply, From, Reply} tuple in an action list in the │ │ │ │ {keep_state, ...} tuple that retains the current state. This return form is │ │ │ │ convenient when you want to stay in the current state but do not know or care │ │ │ │ about what it is.

      If the common state callback needs to know the current state a function │ │ │ │ -handle_common/4 can be used instead:

      -define(HANDLE_COMMON,
      │ │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

      │ │ │ │ +handle_common/4 can be used instead:

      -define(HANDLE_COMMON,
      │ │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ One State Callback │ │ │ │

      │ │ │ │

      If callback mode handle_event_function is used, │ │ │ │ all events are handled in │ │ │ │ Module:handle_event/4 and we can │ │ │ │ (but do not have to) use an event-centered approach where we first branch │ │ │ │ depending on event and then depending on state:

      ...
      │ │ │ │ --export([handle_event/4]).
      │ │ │ │ +-export([handle_event/4]).
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -callback_mode() ->
      │ │ │ │ +callback_mode() ->
      │ │ │ │      handle_event_function.
      │ │ │ │  
      │ │ │ │ -handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
      │ │ │ │ +handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
      │ │ │ │      case State of
      │ │ │ │  	locked ->
      │ │ │ │ -            #{length := Length, buttons := Buttons} = Data,
      │ │ │ │ +            #{length := Length, buttons := Buttons} = Data,
      │ │ │ │              NewButtons =
      │ │ │ │                  if
      │ │ │ │ -                    length(Buttons) < Length ->
      │ │ │ │ +                    length(Buttons) < Length ->
      │ │ │ │                          Buttons;
      │ │ │ │                      true ->
      │ │ │ │ -                        tl(Buttons)
      │ │ │ │ -                end ++ [Button],
      │ │ │ │ +                        tl(Buttons)
      │ │ │ │ +                end ++ [Button],
      │ │ │ │              if
      │ │ │ │                  NewButtons =:= Code -> % Correct
      │ │ │ │ -                    do_unlock(),
      │ │ │ │ -                    {next_state, open, Data#{buttons := []},
      │ │ │ │ -                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +                    do_unlock(),
      │ │ │ │ +                    {next_state, open, Data#{buttons := []},
      │ │ │ │ +                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │                  true -> % Incomplete | Incorrect
      │ │ │ │ -                    {keep_state, Data#{buttons := NewButtons}}
      │ │ │ │ +                    {keep_state, Data#{buttons := NewButtons}}
      │ │ │ │              end;
      │ │ │ │  	open ->
      │ │ │ │              keep_state_and_data
      │ │ │ │      end;
      │ │ │ │ -handle_event(state_timeout, lock, open, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {next_state, locked, Data};
      │ │ │ │ -handle_event(
      │ │ │ │ -  {call,From}, code_length, _State, #{code := Code} = Data) ->
      │ │ │ │ -    {keep_state, Data,
      │ │ │ │ -     [{reply,From,length(Code)}]}.
      │ │ │ │ +handle_event(state_timeout, lock, open, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {next_state, locked, Data};
      │ │ │ │ +handle_event(
      │ │ │ │ +  {call,From}, code_length, _State, #{code := Code} = Data) ->
      │ │ │ │ +    {keep_state, Data,
      │ │ │ │ +     [{reply,From,length(Code)}]}.
      │ │ │ │  
      │ │ │ │  ...

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │ │

      │ │ │ │ @@ -668,59 +668,59 @@ │ │ │ │ │ │ │ │

      If the gen_statem is part of a supervision tree, no stop function is needed. │ │ │ │ The gen_statem is automatically terminated by its supervisor. Exactly how │ │ │ │ this is done is defined by a shutdown strategy │ │ │ │ set in the supervisor.

      If it is necessary to clean up before termination, the shutdown strategy │ │ │ │ must be a time-out value and the gen_statem must in function init/1 │ │ │ │ set itself to trap exit signals by calling │ │ │ │ -process_flag(trap_exit, true):

      init(Args) ->
      │ │ │ │ -    process_flag(trap_exit, true),
      │ │ │ │ -    do_lock(),
      │ │ │ │ +process_flag(trap_exit, true):

      init(Args) ->
      │ │ │ │ +    process_flag(trap_exit, true),
      │ │ │ │ +    do_lock(),
      │ │ │ │      ...

      When ordered to shut down, the gen_statem then calls callback function │ │ │ │ terminate(shutdown, State, Data).

      In this example, function terminate/3 locks the door if it is open, │ │ │ │ so we do not accidentally leave the door open │ │ │ │ -when the supervision tree terminates:

      terminate(_Reason, State, _Data) ->
      │ │ │ │ -    State =/= locked andalso do_lock(),
      │ │ │ │ +when the supervision tree terminates:

      terminate(_Reason, State, _Data) ->
      │ │ │ │ +    State =/= locked andalso do_lock(),
      │ │ │ │      ok.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone gen_statem │ │ │ │

      │ │ │ │

      If the gen_statem is not part of a supervision tree, it can be stopped │ │ │ │ using gen_statem:stop/1, preferably through │ │ │ │ an API function:

      ...
      │ │ │ │ --export([start_link/1,stop/0]).
      │ │ │ │ +-export([start_link/1,stop/0]).
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │ -stop() ->
      │ │ │ │ -    gen_statem:stop(?NAME).

      This makes the gen_statem call callback function terminate/3 just like │ │ │ │ +stop() -> │ │ │ │ + gen_statem:stop(?NAME).

      This makes the gen_statem call callback function terminate/3 just like │ │ │ │ for a supervised server and waits for the process to terminate.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Event Time-Outs │ │ │ │

      │ │ │ │

      A time-out feature inherited from gen_statem's predecessor gen_fsm, │ │ │ │ is an event time-out, that is, if an event arrives the timer is canceled. │ │ │ │ You get either an event or a time-out, but not both.

      It is ordered by the │ │ │ │ transition action {timeout, Time, EventContent}, │ │ │ │ or just an integer Time, even without the enclosing actions list (the latter │ │ │ │ is a form inherited from gen_fsm).

      This type of time-out is useful, for example, to act on inactivity. │ │ │ │ Let's restart the code sequence if no button is pressed for say 30 seconds:

      ...
      │ │ │ │  
      │ │ │ │ -locked(timeout, _, Data) ->
      │ │ │ │ -    {next_state, locked, Data#{buttons := []}};
      │ │ │ │ -locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +locked(timeout, _, Data) ->
      │ │ │ │ +    {next_state, locked, Data#{buttons := []}};
      │ │ │ │ +locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │  ...
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {next_state, locked, Data#{buttons := NewButtons},
      │ │ │ │ -             30_000} % Time in milliseconds
      │ │ │ │ +            {next_state, locked, Data#{buttons := NewButtons},
      │ │ │ │ +             30_000} % Time in milliseconds
      │ │ │ │  ...

      Whenever we receive a button event we start an event time-out of 30 seconds, │ │ │ │ and if we get an event type of timeout we reset the remaining │ │ │ │ code sequence.

      An event time-out is canceled by any other event so you either get │ │ │ │ some other event or the time-out event. Therefore, canceling, │ │ │ │ restarting, or updating an event time-out is neither possible nor │ │ │ │ necessary. Whatever event you act on has already canceled │ │ │ │ the event time-out, so there is never a running event time-out │ │ │ │ @@ -739,30 +739,30 @@ │ │ │ │ another, maybe cancel the time-out without changing states, or perhaps run │ │ │ │ multiple time-outs in parallel. All this can be accomplished with │ │ │ │ generic time-outs. They may look a little │ │ │ │ bit like event time-outs but contain │ │ │ │ a name to allow for any number of them simultaneously and they are │ │ │ │ not automatically canceled.

      Here is how to accomplish the state time-out in the previous example │ │ │ │ by instead using a generic time-out named for example open:

      ...
      │ │ │ │ -locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │  ...
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -	    do_unlock(),
      │ │ │ │ -            {next_state, open, Data#{buttons := []},
      │ │ │ │ -             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +	    do_unlock(),
      │ │ │ │ +            {next_state, open, Data#{buttons := []},
      │ │ │ │ +             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -open({timeout,open}, lock, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {next_state,locked,Data};
      │ │ │ │ -open(cast, {button,_}, Data) ->
      │ │ │ │ -    {keep_state,Data};
      │ │ │ │ +open({timeout,open}, lock, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {next_state,locked,Data};
      │ │ │ │ +open(cast, {button,_}, Data) ->
      │ │ │ │ +    {keep_state,Data};
      │ │ │ │  ...

      Specific generic time-outs can just as state time-outs │ │ │ │ be restarted or canceled by setting it to a new time or infinity.

      In this particular case we do not need to cancel the time-out since │ │ │ │ the time-out event is the only possible reason to do a state change │ │ │ │ from open to locked.

      Instead of bothering with when to cancel a time-out, a late time-out event │ │ │ │ can be handled by ignoring it if it arrives in a state │ │ │ │ where it is known to be late.

      You can restart, cancel, or update a generic time-out. │ │ │ │ See section Time-Outs for details.

      │ │ │ │ @@ -774,32 +774,32 @@ │ │ │ │

      The most versatile way to handle time-outs is to use Erlang Timers; see │ │ │ │ erlang:start_timer/3,4. Most time-out tasks │ │ │ │ can be performed with the time-out features in gen_statem, │ │ │ │ but an example of one that cannot is if you should need the return value │ │ │ │ from erlang:cancel_timer(Tref), that is, │ │ │ │ the remaining time of the timer.

      Here is how to accomplish the state time-out in the previous example │ │ │ │ by instead using an Erlang Timer:

      ...
      │ │ │ │ -locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │  ...
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -	    do_unlock(),
      │ │ │ │ +	    do_unlock(),
      │ │ │ │  	    Tref =
      │ │ │ │ -                 erlang:start_timer(
      │ │ │ │ -                     10_000, self(), lock), % Time in milliseconds
      │ │ │ │ -            {next_state, open, Data#{buttons := [], timer => Tref}};
      │ │ │ │ +                 erlang:start_timer(
      │ │ │ │ +                     10_000, self(), lock), % Time in milliseconds
      │ │ │ │ +            {next_state, open, Data#{buttons := [], timer => Tref}};
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {next_state,locked,maps:remove(timer, Data)};
      │ │ │ │ -open(cast, {button,_}, Data) ->
      │ │ │ │ -    {keep_state,Data};
      │ │ │ │ +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {next_state,locked,maps:remove(timer, Data)};
      │ │ │ │ +open(cast, {button,_}, Data) ->
      │ │ │ │ +    {keep_state,Data};
      │ │ │ │  ...

      Removing the timer key from the map when we do a state change to locked │ │ │ │ is not strictly necessary since we can only get into state open │ │ │ │ with an updated timer map value. But it can be nice to not have │ │ │ │ outdated values in the state Data.

      If you need to cancel a timer because of some other event, you can use │ │ │ │ erlang:cancel_timer(Tref). Note that no time-out │ │ │ │ message will arrive after this (because the timer has been │ │ │ │ explicitly canceled), unless you have already postponed one earlier │ │ │ │ @@ -815,16 +815,16 @@ │ │ │ │ Postponing Events │ │ │ │

      │ │ │ │

      If you want to ignore a particular event in the current state and handle it │ │ │ │ in a future state, you can postpone the event. A postponed event │ │ │ │ is retried after a state change, that is, OldState =/= NewState.

      Postponing is ordered by the │ │ │ │ transition action postpone.

      In this example, instead of ignoring button events while in the open state, │ │ │ │ we can postpone them handle them later in the locked state:

      ...
      │ │ │ │ -open(cast, {button,_}, Data) ->
      │ │ │ │ -    {keep_state,Data,[postpone]};
      │ │ │ │ +open(cast, {button,_}, Data) ->
      │ │ │ │ +    {keep_state,Data,[postpone]};
      │ │ │ │  ...

      Since a postponed event is only retried after a state change, you have to │ │ │ │ think about where to keep a state data item. You can keep it in the server │ │ │ │ Data or in the State itself, for example by having two more or less │ │ │ │ identical states to keep a boolean value, or by using a complex state (see │ │ │ │ section Complex State) with │ │ │ │ callback mode │ │ │ │ handle_event_function. If a change │ │ │ │ @@ -845,55 +845,55 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Selective Receive │ │ │ │ │ │ │ │

      Erlang's selective receive statement is often used to describe simple state │ │ │ │ machine examples in straightforward Erlang code. The following is a possible │ │ │ │ -implementation of the first example:

      -module(code_lock).
      │ │ │ │ --define(NAME, code_lock_1).
      │ │ │ │ --export([start_link/1,button/1]).
      │ │ │ │ -
      │ │ │ │ -start_link(Code) ->
      │ │ │ │ -    spawn(
      │ │ │ │ -      fun () ->
      │ │ │ │ -	      true = register(?NAME, self()),
      │ │ │ │ -	      do_lock(),
      │ │ │ │ -	      locked(Code, length(Code), [])
      │ │ │ │ -      end).
      │ │ │ │ +implementation of the first example:

      -module(code_lock).
      │ │ │ │ +-define(NAME, code_lock_1).
      │ │ │ │ +-export([start_link/1,button/1]).
      │ │ │ │ +
      │ │ │ │ +start_link(Code) ->
      │ │ │ │ +    spawn(
      │ │ │ │ +      fun () ->
      │ │ │ │ +	      true = register(?NAME, self()),
      │ │ │ │ +	      do_lock(),
      │ │ │ │ +	      locked(Code, length(Code), [])
      │ │ │ │ +      end).
      │ │ │ │  
      │ │ │ │ -button(Button) ->
      │ │ │ │ -    ?NAME ! {button,Button}.
      locked(Code, Length, Buttons) ->
      │ │ │ │ +button(Button) ->
      │ │ │ │ +    ?NAME ! {button,Button}.
      locked(Code, Length, Buttons) ->
      │ │ │ │      receive
      │ │ │ │ -        {button,Button} ->
      │ │ │ │ +        {button,Button} ->
      │ │ │ │              NewButtons =
      │ │ │ │                  if
      │ │ │ │ -                    length(Buttons) < Length ->
      │ │ │ │ +                    length(Buttons) < Length ->
      │ │ │ │                          Buttons;
      │ │ │ │                      true ->
      │ │ │ │ -                        tl(Buttons)
      │ │ │ │ -                end ++ [Button],
      │ │ │ │ +                        tl(Buttons)
      │ │ │ │ +                end ++ [Button],
      │ │ │ │              if
      │ │ │ │                  NewButtons =:= Code -> % Correct
      │ │ │ │ -                    do_unlock(),
      │ │ │ │ -		    open(Code, Length);
      │ │ │ │ +                    do_unlock(),
      │ │ │ │ +		    open(Code, Length);
      │ │ │ │                  true -> % Incomplete | Incorrect
      │ │ │ │ -                    locked(Code, Length, NewButtons)
      │ │ │ │ +                    locked(Code, Length, NewButtons)
      │ │ │ │              end
      │ │ │ │ -    end.
      open(Code, Length) ->
      │ │ │ │ +    end.
      open(Code, Length) ->
      │ │ │ │      receive
      │ │ │ │      after 10_000 -> % Time in milliseconds
      │ │ │ │ -	    do_lock(),
      │ │ │ │ -	    locked(Code, Length, [])
      │ │ │ │ +	    do_lock(),
      │ │ │ │ +	    locked(Code, Length, [])
      │ │ │ │      end.
      │ │ │ │  
      │ │ │ │ -do_lock() ->
      │ │ │ │ -    io:format("Locked~n", []).
      │ │ │ │ -do_unlock() ->
      │ │ │ │ -    io:format("Open~n", []).

      The selective receive in this case causes open to implicitly postpone any │ │ │ │ +do_lock() -> │ │ │ │ + io:format("Locked~n", []). │ │ │ │ +do_unlock() -> │ │ │ │ + io:format("Open~n", []).

      The selective receive in this case causes open to implicitly postpone any │ │ │ │ events to the locked state.

      A catch-all receive should never be used from a gen_statem behaviour │ │ │ │ (or from any gen_* behaviour), as the receive statement is within │ │ │ │ the gen_* engine itself. sys-compatible behaviours must respond to │ │ │ │ system messages and therefore do that in their engine receive loop, │ │ │ │ passing non-system messages to the callback module. Using a catch-all │ │ │ │ receive can result in system messages being discarded, which in turn │ │ │ │ can lead to unexpected behaviour. If a selective receive must be used, │ │ │ │ @@ -916,40 +916,40 @@ │ │ │ │ section), especially if only one or a few states have state enter actions, │ │ │ │ this is a perfect use case for the built in │ │ │ │ state enter calls.

      You return a list containing state_enter from your │ │ │ │ callback_mode/0 function and the │ │ │ │ gen_statem engine will call your state callback once with an event │ │ │ │ (enter, OldState, ...) whenever it does a state change. Then you │ │ │ │ just need to handle these event-like calls in all states.

      ...
      │ │ │ │ -init(Code) ->
      │ │ │ │ -    process_flag(trap_exit, true),
      │ │ │ │ -    Data = #{code => Code, length = length(Code)},
      │ │ │ │ -    {ok, locked, Data}.
      │ │ │ │ -
      │ │ │ │ -callback_mode() ->
      │ │ │ │ -    [state_functions,state_enter].
      │ │ │ │ -
      │ │ │ │ -locked(enter, _OldState, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {keep_state,Data#{buttons => []}};
      │ │ │ │ -locked(
      │ │ │ │ -  cast, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +init(Code) ->
      │ │ │ │ +    process_flag(trap_exit, true),
      │ │ │ │ +    Data = #{code => Code, length = length(Code)},
      │ │ │ │ +    {ok, locked, Data}.
      │ │ │ │ +
      │ │ │ │ +callback_mode() ->
      │ │ │ │ +    [state_functions,state_enter].
      │ │ │ │ +
      │ │ │ │ +locked(enter, _OldState, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {keep_state,Data#{buttons => []}};
      │ │ │ │ +locked(
      │ │ │ │ +  cast, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │  ...
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -            {next_state, open, Data};
      │ │ │ │ +            {next_state, open, Data};
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -open(enter, _OldState, _Data) ->
      │ │ │ │ -    do_unlock(),
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ -open(state_timeout, lock, Data) ->
      │ │ │ │ -    {next_state, locked, Data};
      │ │ │ │ +open(enter, _OldState, _Data) ->
      │ │ │ │ +    do_unlock(),
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +open(state_timeout, lock, Data) ->
      │ │ │ │ +    {next_state, locked, Data};
      │ │ │ │  ...

      You can repeat the state enter code by returning one of │ │ │ │ {repeat_state, ...},{repeat_state_and_data, _}, │ │ │ │ or repeat_state_and_data that otherwise behaves exactly like their │ │ │ │ keep_state siblings. See the type │ │ │ │ state_callback_result() │ │ │ │ in the Reference Manual.

      │ │ │ │ │ │ │ │ @@ -971,44 +971,44 @@ │ │ │ │ to dispatch pre-processed events as internal events to the main state │ │ │ │ machine.

      Using internal events also can make it easier to synchronize the state │ │ │ │ machines.

      A variant of this is to use a complex state with │ │ │ │ one state callback, modeling the state │ │ │ │ with, for example, a tuple {MainFSMState, SubFSMState}.

      To illustrate this we make up an example where the buttons instead generate │ │ │ │ down and up (press and release) events, and the lock responds │ │ │ │ to an up event only after the corresponding down event.

      ...
      │ │ │ │ --export([down/1, up/1]).
      │ │ │ │ +-export([down/1, up/1]).
      │ │ │ │  ...
      │ │ │ │ -down(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
      │ │ │ │ +down(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
      │ │ │ │  
      │ │ │ │ -up(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
      │ │ │ │ +up(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
      │ │ │ │  
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -locked(enter, _OldState, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {keep_state,Data#{buttons => []}};
      │ │ │ │ -locked(
      │ │ │ │ -  internal, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ -...
      handle_common(cast, {down,Button}, Data) ->
      │ │ │ │ -    {keep_state, Data#{button => Button}};
      │ │ │ │ -handle_common(cast, {up,Button}, Data) ->
      │ │ │ │ +locked(enter, _OldState, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {keep_state,Data#{buttons => []}};
      │ │ │ │ +locked(
      │ │ │ │ +  internal, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +...
      handle_common(cast, {down,Button}, Data) ->
      │ │ │ │ +    {keep_state, Data#{button => Button}};
      │ │ │ │ +handle_common(cast, {up,Button}, Data) ->
      │ │ │ │      case Data of
      │ │ │ │ -        #{button := Button} ->
      │ │ │ │ -            {keep_state,maps:remove(button, Data),
      │ │ │ │ -             [{next_event,internal,{button,Button}}]};
      │ │ │ │ -        #{} ->
      │ │ │ │ +        #{button := Button} ->
      │ │ │ │ +            {keep_state,maps:remove(button, Data),
      │ │ │ │ +             [{next_event,internal,{button,Button}}]};
      │ │ │ │ +        #{} ->
      │ │ │ │              keep_state_and_data
      │ │ │ │      end;
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -open(internal, {button,_}, Data) ->
      │ │ │ │ -    {keep_state,Data,[postpone]};
      │ │ │ │ +open(internal, {button,_}, Data) ->
      │ │ │ │ +    {keep_state,Data,[postpone]};
      │ │ │ │  ...

      If you start this program with code_lock:start([17]) you can unlock with │ │ │ │ code_lock:down(17), code_lock:up(17).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example Revisited │ │ │ │

      │ │ │ │ @@ -1036,152 +1036,152 @@ │ │ │ │ Also, the state diagram does not show that the code_length/0 call │ │ │ │ must be handled in every state.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: state_functions │ │ │ │

      │ │ │ │ -

      Using state functions:

      -module(code_lock).
      │ │ │ │ --behaviour(gen_statem).
      │ │ │ │ --define(NAME, code_lock_2).
      │ │ │ │ +

      Using state functions:

      -module(code_lock).
      │ │ │ │ +-behaviour(gen_statem).
      │ │ │ │ +-define(NAME, code_lock_2).
      │ │ │ │  
      │ │ │ │ --export([start_link/1,stop/0]).
      │ │ │ │ --export([down/1,up/1,code_length/0]).
      │ │ │ │ --export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ --export([locked/3,open/3]).
      │ │ │ │ -
      │ │ │ │ -start_link(Code) ->
      │ │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
      │ │ │ │ -stop() ->
      │ │ │ │ -    gen_statem:stop(?NAME).
      │ │ │ │ -
      │ │ │ │ -down(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
      │ │ │ │ -up(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
      │ │ │ │ -code_length() ->
      │ │ │ │ -    gen_statem:call(?NAME, code_length).
      init(Code) ->
      │ │ │ │ -    process_flag(trap_exit, true),
      │ │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ -    {ok, locked, Data}.
      │ │ │ │ +-export([start_link/1,stop/0]).
      │ │ │ │ +-export([down/1,up/1,code_length/0]).
      │ │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ +-export([locked/3,open/3]).
      │ │ │ │ +
      │ │ │ │ +start_link(Code) ->
      │ │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
      │ │ │ │ +stop() ->
      │ │ │ │ +    gen_statem:stop(?NAME).
      │ │ │ │ +
      │ │ │ │ +down(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
      │ │ │ │ +up(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
      │ │ │ │ +code_length() ->
      │ │ │ │ +    gen_statem:call(?NAME, code_length).
      init(Code) ->
      │ │ │ │ +    process_flag(trap_exit, true),
      │ │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ +    {ok, locked, Data}.
      │ │ │ │  
      │ │ │ │ -callback_mode() ->
      │ │ │ │ -    [state_functions,state_enter].
      │ │ │ │ +callback_mode() ->
      │ │ │ │ +    [state_functions,state_enter].
      │ │ │ │  
      │ │ │ │ --define(HANDLE_COMMON,
      │ │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
      │ │ │ │ +-define(HANDLE_COMMON,
      │ │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
      │ │ │ │  %%
      │ │ │ │ -handle_common(cast, {down,Button}, Data) ->
      │ │ │ │ -    {keep_state, Data#{button => Button}};
      │ │ │ │ -handle_common(cast, {up,Button}, Data) ->
      │ │ │ │ +handle_common(cast, {down,Button}, Data) ->
      │ │ │ │ +    {keep_state, Data#{button => Button}};
      │ │ │ │ +handle_common(cast, {up,Button}, Data) ->
      │ │ │ │      case Data of
      │ │ │ │ -        #{button := Button} ->
      │ │ │ │ -            {keep_state, maps:remove(button, Data),
      │ │ │ │ -             [{next_event,internal,{button,Button}}]};
      │ │ │ │ -        #{} ->
      │ │ │ │ +        #{button := Button} ->
      │ │ │ │ +            {keep_state, maps:remove(button, Data),
      │ │ │ │ +             [{next_event,internal,{button,Button}}]};
      │ │ │ │ +        #{} ->
      │ │ │ │              keep_state_and_data
      │ │ │ │      end;
      │ │ │ │ -handle_common({call,From}, code_length, #{code := Code}) ->
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{reply,From,length(Code)}]}.
      locked(enter, _OldState, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -locked(state_timeout, button, Data) ->
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -locked(
      │ │ │ │ -  internal, {button,Button},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +handle_common({call,From}, code_length, #{code := Code}) ->
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{reply,From,length(Code)}]}.
      locked(enter, _OldState, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +locked(state_timeout, button, Data) ->
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +locked(
      │ │ │ │ +  internal, {button,Button},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │      NewButtons =
      │ │ │ │          if
      │ │ │ │ -            length(Buttons) < Length ->
      │ │ │ │ +            length(Buttons) < Length ->
      │ │ │ │                  Buttons;
      │ │ │ │              true ->
      │ │ │ │ -                tl(Buttons)
      │ │ │ │ -        end ++ [Button],
      │ │ │ │ +                tl(Buttons)
      │ │ │ │ +        end ++ [Button],
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -            {next_state, open, Data};
      │ │ │ │ +            {next_state, open, Data};
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │ +            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │      end;
      │ │ │ │ -?HANDLE_COMMON.
      open(enter, _OldState, _Data) ->
      │ │ │ │ -    do_unlock(),
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ -open(state_timeout, lock, Data) ->
      │ │ │ │ -    {next_state, locked, Data};
      │ │ │ │ -open(internal, {button,_}, _) ->
      │ │ │ │ -    {keep_state_and_data, [postpone]};
      │ │ │ │ +?HANDLE_COMMON.
      open(enter, _OldState, _Data) ->
      │ │ │ │ +    do_unlock(),
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +open(state_timeout, lock, Data) ->
      │ │ │ │ +    {next_state, locked, Data};
      │ │ │ │ +open(internal, {button,_}, _) ->
      │ │ │ │ +    {keep_state_and_data, [postpone]};
      │ │ │ │  ?HANDLE_COMMON.
      │ │ │ │  
      │ │ │ │ -do_lock() ->
      │ │ │ │ -    io:format("Locked~n", []).
      │ │ │ │ -do_unlock() ->
      │ │ │ │ -    io:format("Open~n", []).
      │ │ │ │ +do_lock() ->
      │ │ │ │ +    io:format("Locked~n", []).
      │ │ │ │ +do_unlock() ->
      │ │ │ │ +    io:format("Open~n", []).
      │ │ │ │  
      │ │ │ │ -terminate(_Reason, State, _Data) ->
      │ │ │ │ -    State =/= locked andalso do_lock(),
      │ │ │ │ +terminate(_Reason, State, _Data) ->
      │ │ │ │ +    State =/= locked andalso do_lock(),
      │ │ │ │      ok.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: handle_event_function │ │ │ │

      │ │ │ │

      This section describes what to change in the example to use one │ │ │ │ handle_event/4 function. The previously used approach to first branch │ │ │ │ depending on event does not work that well here because of │ │ │ │ -the state enter calls, so this example first branches depending on state:

      -export([handle_event/4]).
      callback_mode() ->
      │ │ │ │ -    [handle_event_function,state_enter].
      %%
      │ │ │ │ +the state enter calls, so this example first branches depending on state:

      -export([handle_event/4]).
      callback_mode() ->
      │ │ │ │ +    [handle_event_function,state_enter].
      %%
      │ │ │ │  %% State: locked
      │ │ │ │ -handle_event(enter, _OldState, locked, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -handle_event(state_timeout, button, locked, Data) ->
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -handle_event(
      │ │ │ │ -  internal, {button,Button}, locked,
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +handle_event(enter, _OldState, locked, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +handle_event(state_timeout, button, locked, Data) ->
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +handle_event(
      │ │ │ │ +  internal, {button,Button}, locked,
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │      NewButtons =
      │ │ │ │          if
      │ │ │ │ -            length(Buttons) < Length ->
      │ │ │ │ +            length(Buttons) < Length ->
      │ │ │ │                  Buttons;
      │ │ │ │              true ->
      │ │ │ │ -                tl(Buttons)
      │ │ │ │ -        end ++ [Button],
      │ │ │ │ +                tl(Buttons)
      │ │ │ │ +        end ++ [Button],
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -            {next_state, open, Data};
      │ │ │ │ +            {next_state, open, Data};
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │ +            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │      end;
      %%
      │ │ │ │  %% State: open
      │ │ │ │ -handle_event(enter, _OldState, open, _Data) ->
      │ │ │ │ -    do_unlock(),
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ -handle_event(state_timeout, lock, open, Data) ->
      │ │ │ │ -    {next_state, locked, Data};
      │ │ │ │ -handle_event(internal, {button,_}, open, _) ->
      │ │ │ │ -    {keep_state_and_data,[postpone]};
      %% Common events
      │ │ │ │ -handle_event(cast, {down,Button}, _State, Data) ->
      │ │ │ │ -    {keep_state, Data#{button => Button}};
      │ │ │ │ -handle_event(cast, {up,Button}, _State, Data) ->
      │ │ │ │ +handle_event(enter, _OldState, open, _Data) ->
      │ │ │ │ +    do_unlock(),
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +handle_event(state_timeout, lock, open, Data) ->
      │ │ │ │ +    {next_state, locked, Data};
      │ │ │ │ +handle_event(internal, {button,_}, open, _) ->
      │ │ │ │ +    {keep_state_and_data,[postpone]};
      %% Common events
      │ │ │ │ +handle_event(cast, {down,Button}, _State, Data) ->
      │ │ │ │ +    {keep_state, Data#{button => Button}};
      │ │ │ │ +handle_event(cast, {up,Button}, _State, Data) ->
      │ │ │ │      case Data of
      │ │ │ │ -        #{button := Button} ->
      │ │ │ │ -            {keep_state, maps:remove(button, Data),
      │ │ │ │ -             [{next_event,internal,{button,Button}},
      │ │ │ │ -              {state_timeout,30_000,button}]}; % Time in milliseconds
      │ │ │ │ -        #{} ->
      │ │ │ │ +        #{button := Button} ->
      │ │ │ │ +            {keep_state, maps:remove(button, Data),
      │ │ │ │ +             [{next_event,internal,{button,Button}},
      │ │ │ │ +              {state_timeout,30_000,button}]}; % Time in milliseconds
      │ │ │ │ +        #{} ->
      │ │ │ │              keep_state_and_data
      │ │ │ │      end;
      │ │ │ │ -handle_event({call,From}, code_length, _State, #{length := Length}) ->
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{reply,From,Length}]}.

      Notice that postponing buttons from the open state to the locked state │ │ │ │ +handle_event({call,From}, code_length, _State, #{length := Length}) -> │ │ │ │ + {keep_state_and_data, │ │ │ │ + [{reply,From,Length}]}.

      Notice that postponing buttons from the open state to the locked state │ │ │ │ seems like a strange thing to do for a code lock, but it at least │ │ │ │ illustrates event postponing.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Filter the State │ │ │ │

      │ │ │ │ @@ -1191,30 +1191,30 @@ │ │ │ │ and which digits that remain to unlock.

      This state data can be regarded as sensitive, and maybe not what you want │ │ │ │ in the error log because of some unpredictable event.

      Another reason to filter the state can be that the state is too large to print, │ │ │ │ as it fills the error log with uninteresting details.

      To avoid this, you can format the internal state that gets in the error log │ │ │ │ and gets returned from sys:get_status/1,2 │ │ │ │ by implementing function │ │ │ │ Module:format_status/2, │ │ │ │ for example like this:

      ...
      │ │ │ │ --export([init/1,terminate/3,format_status/2]).
      │ │ │ │ +-export([init/1,terminate/3,format_status/2]).
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ -format_status(Opt, [_PDict,State,Data]) ->
      │ │ │ │ +format_status(Opt, [_PDict,State,Data]) ->
      │ │ │ │      StateData =
      │ │ │ │ -	{State,
      │ │ │ │ -	 maps:filter(
      │ │ │ │ -	   fun (code, _) -> false;
      │ │ │ │ -	       (_, _) -> true
      │ │ │ │ +	{State,
      │ │ │ │ +	 maps:filter(
      │ │ │ │ +	   fun (code, _) -> false;
      │ │ │ │ +	       (_, _) -> true
      │ │ │ │  	   end,
      │ │ │ │ -	   Data)},
      │ │ │ │ +	   Data)},
      │ │ │ │      case Opt of
      │ │ │ │  	terminate ->
      │ │ │ │  	    StateData;
      │ │ │ │  	normal ->
      │ │ │ │ -	    [{data,[{"State",StateData}]}]
      │ │ │ │ +	    [{data,[{"State",StateData}]}]
      │ │ │ │      end.

      It is not mandatory to implement a │ │ │ │ Module:format_status/2 function. │ │ │ │ If you do not, a default implementation is used that does the same │ │ │ │ as this example function without filtering the Data term, that is, │ │ │ │ StateData = {State, Data}, in this example containing sensitive information.

      │ │ │ │ │ │ │ │ │ │ │ │ @@ -1227,104 +1227,104 @@ │ │ │ │ like a tuple.

      One reason to use this is when you have a state item that when changed │ │ │ │ should cancel the state time-out, or one that affects │ │ │ │ the event handling in combination with postponing events. We will go for │ │ │ │ the latter and complicate the previous example by introducing │ │ │ │ a configurable lock button (this is the state item in question), │ │ │ │ which in the open state immediately locks the door, and an API function │ │ │ │ set_lock_button/1 to set the lock button.

      Suppose now that we call set_lock_button while the door is open, │ │ │ │ -and we have already postponed a button event that was the new lock button:

      1> code_lock:start_link([a,b,c], x).
      │ │ │ │ -{ok,<0.666.0>}
      │ │ │ │ -2> code_lock:button(a).
      │ │ │ │ +and we have already postponed a button event that was the new lock button:

      1> code_lock:start_link([a,b,c], x).
      │ │ │ │ +{ok,<0.666.0>}
      │ │ │ │ +2> code_lock:button(a).
      │ │ │ │  ok
      │ │ │ │ -3> code_lock:button(b).
      │ │ │ │ +3> code_lock:button(b).
      │ │ │ │  ok
      │ │ │ │ -4> code_lock:button(c).
      │ │ │ │ +4> code_lock:button(c).
      │ │ │ │  ok
      │ │ │ │  Open
      │ │ │ │ -5> code_lock:button(y).
      │ │ │ │ +5> code_lock:button(y).
      │ │ │ │  ok
      │ │ │ │ -6> code_lock:set_lock_button(y).
      │ │ │ │ +6> code_lock:set_lock_button(y).
      │ │ │ │  x
      │ │ │ │  % What should happen here?  Immediate lock or nothing?

      We could say that the button was pressed too early so it should not be │ │ │ │ recognized as the lock button. Or we can make the lock button part of │ │ │ │ the state so when we then change the lock button in the locked state, │ │ │ │ the change becomes a state change and all postponed events are retried, │ │ │ │ therefore the lock is immediately locked!

      We define the state as {StateName, LockButton}, where StateName │ │ │ │ -is as before and LockButton is the current lock button:

      -module(code_lock).
      │ │ │ │ --behaviour(gen_statem).
      │ │ │ │ --define(NAME, code_lock_3).
      │ │ │ │ +is as before and LockButton is the current lock button:

      -module(code_lock).
      │ │ │ │ +-behaviour(gen_statem).
      │ │ │ │ +-define(NAME, code_lock_3).
      │ │ │ │  
      │ │ │ │ --export([start_link/2,stop/0]).
      │ │ │ │ --export([button/1,set_lock_button/1]).
      │ │ │ │ --export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ --export([handle_event/4]).
      │ │ │ │ -
      │ │ │ │ -start_link(Code, LockButton) ->
      │ │ │ │ -    gen_statem:start_link(
      │ │ │ │ -        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
      │ │ │ │ -stop() ->
      │ │ │ │ -    gen_statem:stop(?NAME).
      │ │ │ │ -
      │ │ │ │ -button(Button) ->
      │ │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
      │ │ │ │ -set_lock_button(LockButton) ->
      │ │ │ │ -    gen_statem:call(?NAME, {set_lock_button,LockButton}).
      init({Code,LockButton}) ->
      │ │ │ │ -    process_flag(trap_exit, true),
      │ │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ -    {ok, {locked,LockButton}, Data}.
      │ │ │ │ +-export([start_link/2,stop/0]).
      │ │ │ │ +-export([button/1,set_lock_button/1]).
      │ │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
      │ │ │ │ +-export([handle_event/4]).
      │ │ │ │ +
      │ │ │ │ +start_link(Code, LockButton) ->
      │ │ │ │ +    gen_statem:start_link(
      │ │ │ │ +        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
      │ │ │ │ +stop() ->
      │ │ │ │ +    gen_statem:stop(?NAME).
      │ │ │ │ +
      │ │ │ │ +button(Button) ->
      │ │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
      │ │ │ │ +set_lock_button(LockButton) ->
      │ │ │ │ +    gen_statem:call(?NAME, {set_lock_button,LockButton}).
      init({Code,LockButton}) ->
      │ │ │ │ +    process_flag(trap_exit, true),
      │ │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
      │ │ │ │ +    {ok, {locked,LockButton}, Data}.
      │ │ │ │  
      │ │ │ │ -callback_mode() ->
      │ │ │ │ -    [handle_event_function,state_enter].
      │ │ │ │ +callback_mode() ->
      │ │ │ │ +    [handle_event_function,state_enter].
      │ │ │ │  
      │ │ │ │  %% State: locked
      │ │ │ │ -handle_event(enter, _OldState, {locked,_}, Data) ->
      │ │ │ │ -    do_lock(),
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -handle_event(state_timeout, button, {locked,_}, Data) ->
      │ │ │ │ -    {keep_state, Data#{buttons := []}};
      │ │ │ │ -handle_event(
      │ │ │ │ -  cast, {button,Button}, {locked,LockButton},
      │ │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │ +handle_event(enter, _OldState, {locked,_}, Data) ->
      │ │ │ │ +    do_lock(),
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +handle_event(state_timeout, button, {locked,_}, Data) ->
      │ │ │ │ +    {keep_state, Data#{buttons := []}};
      │ │ │ │ +handle_event(
      │ │ │ │ +  cast, {button,Button}, {locked,LockButton},
      │ │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
      │ │ │ │      NewButtons =
      │ │ │ │          if
      │ │ │ │ -            length(Buttons) < Length ->
      │ │ │ │ +            length(Buttons) < Length ->
      │ │ │ │                  Buttons;
      │ │ │ │              true ->
      │ │ │ │ -                tl(Buttons)
      │ │ │ │ -        end ++ [Button],
      │ │ │ │ +                tl(Buttons)
      │ │ │ │ +        end ++ [Button],
      │ │ │ │      if
      │ │ │ │          NewButtons =:= Code -> % Correct
      │ │ │ │ -            {next_state, {open,LockButton}, Data};
      │ │ │ │ +            {next_state, {open,LockButton}, Data};
      │ │ │ │  	true -> % Incomplete | Incorrect
      │ │ │ │ -            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │ +            {keep_state, Data#{buttons := NewButtons},
      │ │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
      │ │ │ │      end;
      %%
      │ │ │ │  %% State: open
      │ │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
      │ │ │ │ -    do_unlock(),
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ -handle_event(state_timeout, lock, {open,LockButton}, Data) ->
      │ │ │ │ -    {next_state, {locked,LockButton}, Data};
      │ │ │ │ -handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
      │ │ │ │ -    {next_state, {locked,LockButton}, Data};
      │ │ │ │ -handle_event(cast, {button,_}, {open,_}, _Data) ->
      │ │ │ │ -    {keep_state_and_data,[postpone]};
      %%
      │ │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
      │ │ │ │ +    do_unlock(),
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
      │ │ │ │ +handle_event(state_timeout, lock, {open,LockButton}, Data) ->
      │ │ │ │ +    {next_state, {locked,LockButton}, Data};
      │ │ │ │ +handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
      │ │ │ │ +    {next_state, {locked,LockButton}, Data};
      │ │ │ │ +handle_event(cast, {button,_}, {open,_}, _Data) ->
      │ │ │ │ +    {keep_state_and_data,[postpone]};
      %%
      │ │ │ │  %% Common events
      │ │ │ │ -handle_event(
      │ │ │ │ -  {call,From}, {set_lock_button,NewLockButton},
      │ │ │ │ -  {StateName,OldLockButton}, Data) ->
      │ │ │ │ -    {next_state, {StateName,NewLockButton}, Data,
      │ │ │ │ -     [{reply,From,OldLockButton}]}.
      do_lock() ->
      │ │ │ │ -    io:format("Locked~n", []).
      │ │ │ │ -do_unlock() ->
      │ │ │ │ -    io:format("Open~n", []).
      │ │ │ │ +handle_event(
      │ │ │ │ +  {call,From}, {set_lock_button,NewLockButton},
      │ │ │ │ +  {StateName,OldLockButton}, Data) ->
      │ │ │ │ +    {next_state, {StateName,NewLockButton}, Data,
      │ │ │ │ +     [{reply,From,OldLockButton}]}.
      do_lock() ->
      │ │ │ │ +    io:format("Locked~n", []).
      │ │ │ │ +do_unlock() ->
      │ │ │ │ +    io:format("Open~n", []).
      │ │ │ │  
      │ │ │ │ -terminate(_Reason, State, _Data) ->
      │ │ │ │ -    State =/= locked andalso do_lock(),
      │ │ │ │ +terminate(_Reason, State, _Data) ->
      │ │ │ │ +    State =/= locked andalso do_lock(),
      │ │ │ │      ok.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Hibernation │ │ │ │

      │ │ │ │

      If you have many servers in one node and they have some state(s) in their │ │ │ │ @@ -1333,19 +1333,19 @@ │ │ │ │ footprint of a server can be minimized by hibernating it through │ │ │ │ proc_lib:hibernate/3.

      Note

      It is rather costly to hibernate a process; see erlang:hibernate/3. It is │ │ │ │ not something you want to do after every event.

      We can in this example hibernate in the {open, _} state, │ │ │ │ because what normally occurs in that state is that the state time-out │ │ │ │ after a while triggers a transition to {locked, _}:

      ...
      │ │ │ │  %%
      │ │ │ │  %% State: open
      │ │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
      │ │ │ │ -    do_unlock(),
      │ │ │ │ -    {keep_state_and_data,
      │ │ │ │ -     [{state_timeout,10_000,lock}, % Time in milliseconds
      │ │ │ │ -      hibernate]};
      │ │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
      │ │ │ │ +    do_unlock(),
      │ │ │ │ +    {keep_state_and_data,
      │ │ │ │ +     [{state_timeout,10_000,lock}, % Time in milliseconds
      │ │ │ │ +      hibernate]};
      │ │ │ │  ...

      The atom hibernate in the action list on the │ │ │ │ last line when entering the {open, _} state is the only change. If any event │ │ │ │ arrives in the {open, _}, state, we do not bother to rehibernate, │ │ │ │ so the server stays awake after any event.

      To change that we would need to insert action hibernate in more places. │ │ │ │ For example, the state-independent set_lock_button operation │ │ │ │ would have to use hibernate but only in the {open, _} state, │ │ │ │ which would clutter the code.

      Another not uncommon scenario is to use the │ │ │ ├── OEBPS/spec_proc.xhtml │ │ │ │ @@ -28,72 +28,72 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Debugging │ │ │ │

      │ │ │ │

      The sys module has functions for simple debugging of processes implemented │ │ │ │ using behaviours. The code_lock example from │ │ │ │ -gen_statem Behaviour is used to illustrate this:

      Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
      │ │ │ │ +gen_statem Behaviour is used to illustrate this:

      Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
      │ │ │ │  
      │ │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ -1> code_lock:start_link([1,2,3,4]).
      │ │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ +1> code_lock:start_link([1,2,3,4]).
      │ │ │ │  Lock
      │ │ │ │ -{ok,<0.90.0>}
      │ │ │ │ -2> sys:statistics(code_lock, true).
      │ │ │ │ +{ok,<0.90.0>}
      │ │ │ │ +2> sys:statistics(code_lock, true).
      │ │ │ │  ok
      │ │ │ │ -3> sys:trace(code_lock, true).
      │ │ │ │ +3> sys:trace(code_lock, true).
      │ │ │ │  ok
      │ │ │ │ -4> code_lock:button(1).
      │ │ │ │ -*DBG* code_lock receive cast {button,1} in state locked
      │ │ │ │ +4> code_lock:button(1).
      │ │ │ │ +*DBG* code_lock receive cast {button,1} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,1} in state locked
      │ │ │ │ -5> code_lock:button(2).
      │ │ │ │ -*DBG* code_lock receive cast {button,2} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,1} in state locked
      │ │ │ │ +5> code_lock:button(2).
      │ │ │ │ +*DBG* code_lock receive cast {button,2} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,2} in state locked
      │ │ │ │ -6> code_lock:button(3).
      │ │ │ │ -*DBG* code_lock receive cast {button,3} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,2} in state locked
      │ │ │ │ +6> code_lock:button(3).
      │ │ │ │ +*DBG* code_lock receive cast {button,3} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,3} in state locked
      │ │ │ │ -7> code_lock:button(4).
      │ │ │ │ -*DBG* code_lock receive cast {button,4} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,3} in state locked
      │ │ │ │ +7> code_lock:button(4).
      │ │ │ │ +*DBG* code_lock receive cast {button,4} in state locked
      │ │ │ │  ok
      │ │ │ │  Unlock
      │ │ │ │ -*DBG* code_lock consume cast {button,4} in state locked => open
      │ │ │ │ -*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
      │ │ │ │ +*DBG* code_lock consume cast {button,4} in state locked => open
      │ │ │ │ +*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
      │ │ │ │  *DBG* code_lock receive state_timeout lock in state open
      │ │ │ │  Lock
      │ │ │ │  *DBG* code_lock consume state_timeout lock in state open => locked
      │ │ │ │ -8> sys:statistics(code_lock, get).
      │ │ │ │ -{ok,[{start_time,{{2024,5,3},{8,11,1}}},
      │ │ │ │ -     {current_time,{{2024,5,3},{8,11,48}}},
      │ │ │ │ -     {reductions,4098},
      │ │ │ │ -     {messages_in,5},
      │ │ │ │ -     {messages_out,0}]}
      │ │ │ │ -9> sys:statistics(code_lock, false).
      │ │ │ │ +8> sys:statistics(code_lock, get).
      │ │ │ │ +{ok,[{start_time,{{2024,5,3},{8,11,1}}},
      │ │ │ │ +     {current_time,{{2024,5,3},{8,11,48}}},
      │ │ │ │ +     {reductions,4098},
      │ │ │ │ +     {messages_in,5},
      │ │ │ │ +     {messages_out,0}]}
      │ │ │ │ +9> sys:statistics(code_lock, false).
      │ │ │ │  ok
      │ │ │ │ -10> sys:trace(code_lock, false).
      │ │ │ │ +10> sys:trace(code_lock, false).
      │ │ │ │  ok
      │ │ │ │ -11> sys:get_status(code_lock).
      │ │ │ │ -{status,<0.90.0>,
      │ │ │ │ -        {module,gen_statem},
      │ │ │ │ -        [[{'$initial_call',{code_lock,init,1}},
      │ │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
      │ │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
      │ │ │ │ -         running,<0.88.0>,[],
      │ │ │ │ -         [{header,"Status for state machine code_lock"},
      │ │ │ │ -          {data,[{"Status",running},
      │ │ │ │ -                 {"Parent",<0.88.0>},
      │ │ │ │ -                 {"Modules",[code_lock]},
      │ │ │ │ -                 {"Time-outs",{0,[]}},
      │ │ │ │ -                 {"Logged Events",[]},
      │ │ │ │ -                 {"Postponed",[]}]},
      │ │ │ │ -          {data,[{"State",
      │ │ │ │ -                  {locked,#{code => [1,2,3,4],
      │ │ │ │ -                            length => 4,buttons => []}}}]}]]}

      │ │ │ │ +11> sys:get_status(code_lock). │ │ │ │ +{status,<0.90.0>, │ │ │ │ + {module,gen_statem}, │ │ │ │ + [[{'$initial_call',{code_lock,init,1}}, │ │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ │ + running,<0.88.0>,[], │ │ │ │ + [{header,"Status for state machine code_lock"}, │ │ │ │ + {data,[{"Status",running}, │ │ │ │ + {"Parent",<0.88.0>}, │ │ │ │ + {"Modules",[code_lock]}, │ │ │ │ + {"Time-outs",{0,[]}}, │ │ │ │ + {"Logged Events",[]}, │ │ │ │ + {"Postponed",[]}]}, │ │ │ │ + {data,[{"State", │ │ │ │ + {locked,#{code => [1,2,3,4], │ │ │ │ + length => 4,buttons => []}}}]}]]}

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Special Processes │ │ │ │

      │ │ │ │

      This section describes how to write a process that complies to the OTP design │ │ │ │ principles, without using a standard behaviour. Such a process is to:

      System messages are messages with a special meaning, used in the supervision │ │ │ │ @@ -103,238 +103,238 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │ │ │ │ │

      Here follows the simple server from │ │ │ │ Overview, │ │ │ │ -implemented using sys and proc_lib to fit into a supervision tree:

      -module(ch4).
      │ │ │ │ --export([start_link/0]).
      │ │ │ │ --export([alloc/0, free/1]).
      │ │ │ │ --export([init/1]).
      │ │ │ │ --export([system_continue/3, system_terminate/4,
      │ │ │ │ +implemented using sys and proc_lib to fit into a supervision tree:

      -module(ch4).
      │ │ │ │ +-export([start_link/0]).
      │ │ │ │ +-export([alloc/0, free/1]).
      │ │ │ │ +-export([init/1]).
      │ │ │ │ +-export([system_continue/3, system_terminate/4,
      │ │ │ │           write_debug/3,
      │ │ │ │ -         system_get_state/1, system_replace_state/2]).
      │ │ │ │ +         system_get_state/1, system_replace_state/2]).
      │ │ │ │  
      │ │ │ │ -start_link() ->
      │ │ │ │ -    proc_lib:start_link(ch4, init, [self()]).
      │ │ │ │ +start_link() ->
      │ │ │ │ +    proc_lib:start_link(ch4, init, [self()]).
      │ │ │ │  
      │ │ │ │ -alloc() ->
      │ │ │ │ -    ch4 ! {self(), alloc},
      │ │ │ │ +alloc() ->
      │ │ │ │ +    ch4 ! {self(), alloc},
      │ │ │ │      receive
      │ │ │ │ -        {ch4, Res} ->
      │ │ │ │ +        {ch4, Res} ->
      │ │ │ │              Res
      │ │ │ │      end.
      │ │ │ │  
      │ │ │ │ -free(Ch) ->
      │ │ │ │ -    ch4 ! {free, Ch},
      │ │ │ │ +free(Ch) ->
      │ │ │ │ +    ch4 ! {free, Ch},
      │ │ │ │      ok.
      │ │ │ │  
      │ │ │ │ -init(Parent) ->
      │ │ │ │ -    register(ch4, self()),
      │ │ │ │ -    Chs = channels(),
      │ │ │ │ -    Deb = sys:debug_options([]),
      │ │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
      │ │ │ │ -    loop(Chs, Parent, Deb).
      │ │ │ │ +init(Parent) ->
      │ │ │ │ +    register(ch4, self()),
      │ │ │ │ +    Chs = channels(),
      │ │ │ │ +    Deb = sys:debug_options([]),
      │ │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
      │ │ │ │ +    loop(Chs, Parent, Deb).
      │ │ │ │  
      │ │ │ │ -loop(Chs, Parent, Deb) ->
      │ │ │ │ +loop(Chs, Parent, Deb) ->
      │ │ │ │      receive
      │ │ │ │ -        {From, alloc} ->
      │ │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ │ -                                    ch4, {in, alloc, From}),
      │ │ │ │ -            {Ch, Chs2} = alloc(Chs),
      │ │ │ │ -            From ! {ch4, Ch},
      │ │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ │ -            loop(Chs2, Parent, Deb3);
      │ │ │ │ -        {free, Ch} ->
      │ │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ │ -                                    ch4, {in, {free, Ch}}),
      │ │ │ │ -            Chs2 = free(Ch, Chs),
      │ │ │ │ -            loop(Chs2, Parent, Deb2);
      │ │ │ │ -
      │ │ │ │ -        {system, From, Request} ->
      │ │ │ │ -            sys:handle_system_msg(Request, From, Parent,
      │ │ │ │ -                                  ch4, Deb, Chs)
      │ │ │ │ +        {From, alloc} ->
      │ │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ │ +                                    ch4, {in, alloc, From}),
      │ │ │ │ +            {Ch, Chs2} = alloc(Chs),
      │ │ │ │ +            From ! {ch4, Ch},
      │ │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ │ +            loop(Chs2, Parent, Deb3);
      │ │ │ │ +        {free, Ch} ->
      │ │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ │ +                                    ch4, {in, {free, Ch}}),
      │ │ │ │ +            Chs2 = free(Ch, Chs),
      │ │ │ │ +            loop(Chs2, Parent, Deb2);
      │ │ │ │ +
      │ │ │ │ +        {system, From, Request} ->
      │ │ │ │ +            sys:handle_system_msg(Request, From, Parent,
      │ │ │ │ +                                  ch4, Deb, Chs)
      │ │ │ │      end.
      │ │ │ │  
      │ │ │ │ -system_continue(Parent, Deb, Chs) ->
      │ │ │ │ -    loop(Chs, Parent, Deb).
      │ │ │ │ +system_continue(Parent, Deb, Chs) ->
      │ │ │ │ +    loop(Chs, Parent, Deb).
      │ │ │ │  
      │ │ │ │ -system_terminate(Reason, _Parent, _Deb, _Chs) ->
      │ │ │ │ -    exit(Reason).
      │ │ │ │ +system_terminate(Reason, _Parent, _Deb, _Chs) ->
      │ │ │ │ +    exit(Reason).
      │ │ │ │  
      │ │ │ │ -system_get_state(Chs) ->
      │ │ │ │ -    {ok, Chs}.
      │ │ │ │ +system_get_state(Chs) ->
      │ │ │ │ +    {ok, Chs}.
      │ │ │ │  
      │ │ │ │ -system_replace_state(StateFun, Chs) ->
      │ │ │ │ -    NChs = StateFun(Chs),
      │ │ │ │ -    {ok, NChs, NChs}.
      │ │ │ │ +system_replace_state(StateFun, Chs) ->
      │ │ │ │ +    NChs = StateFun(Chs),
      │ │ │ │ +    {ok, NChs, NChs}.
      │ │ │ │  
      │ │ │ │ -write_debug(Dev, Event, Name) ->
      │ │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

      As it is not relevant to the example, the channel handling functions have been │ │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

      As it is not relevant to the example, the channel handling functions have been │ │ │ │ omitted. To compile this example, the │ │ │ │ implementation of channel handling │ │ │ │ needs to be added to the module.

      Here is an example showing how the debugging functions in the sys │ │ │ │ module can be used for ch4:

      % erl
      │ │ │ │ -Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
      │ │ │ │ +Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
      │ │ │ │  
      │ │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ -1> ch4:start_link().
      │ │ │ │ -{ok,<0.90.0>}
      │ │ │ │ -2> sys:statistics(ch4, true).
      │ │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ +1> ch4:start_link().
      │ │ │ │ +{ok,<0.90.0>}
      │ │ │ │ +2> sys:statistics(ch4, true).
      │ │ │ │  ok
      │ │ │ │ -3> sys:trace(ch4, true).
      │ │ │ │ +3> sys:trace(ch4, true).
      │ │ │ │  ok
      │ │ │ │ -4> ch4:alloc().
      │ │ │ │ -ch4 event = {in,alloc,<0.88.0>}
      │ │ │ │ -ch4 event = {out,{ch4,1},<0.88.0>}
      │ │ │ │ +4> ch4:alloc().
      │ │ │ │ +ch4 event = {in,alloc,<0.88.0>}
      │ │ │ │ +ch4 event = {out,{ch4,1},<0.88.0>}
      │ │ │ │  1
      │ │ │ │ -5> ch4:free(ch1).
      │ │ │ │ -ch4 event = {in,{free,ch1}}
      │ │ │ │ +5> ch4:free(ch1).
      │ │ │ │ +ch4 event = {in,{free,ch1}}
      │ │ │ │  ok
      │ │ │ │ -6> sys:statistics(ch4, get).
      │ │ │ │ -{ok,[{start_time,{{2024,5,3},{8,26,13}}},
      │ │ │ │ -     {current_time,{{2024,5,3},{8,26,49}}},
      │ │ │ │ -     {reductions,202},
      │ │ │ │ -     {messages_in,2},
      │ │ │ │ -     {messages_out,1}]}
      │ │ │ │ -7> sys:statistics(ch4, false).
      │ │ │ │ +6> sys:statistics(ch4, get).
      │ │ │ │ +{ok,[{start_time,{{2024,5,3},{8,26,13}}},
      │ │ │ │ +     {current_time,{{2024,5,3},{8,26,49}}},
      │ │ │ │ +     {reductions,202},
      │ │ │ │ +     {messages_in,2},
      │ │ │ │ +     {messages_out,1}]}
      │ │ │ │ +7> sys:statistics(ch4, false).
      │ │ │ │  ok
      │ │ │ │ -8> sys:trace(ch4, false).
      │ │ │ │ +8> sys:trace(ch4, false).
      │ │ │ │  ok
      │ │ │ │ -9> sys:get_status(ch4).
      │ │ │ │ -{status,<0.90.0>,
      │ │ │ │ -        {module,ch4},
      │ │ │ │ -        [[{'$initial_call',{ch4,init,1}},
      │ │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
      │ │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
      │ │ │ │ -         running,<0.88.0>,[],
      │ │ │ │ -         {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

      │ │ │ │ +9> sys:get_status(ch4). │ │ │ │ +{status,<0.90.0>, │ │ │ │ + {module,ch4}, │ │ │ │ + [[{'$initial_call',{ch4,init,1}}, │ │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ │ + running,<0.88.0>,[], │ │ │ │ + {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting the Process │ │ │ │

      │ │ │ │

      A function in the proc_lib module is to be used to start the process. Several │ │ │ │ functions are available, for example, │ │ │ │ proc_lib:spawn_link/3,4 │ │ │ │ for asynchronous start and │ │ │ │ proc_lib:start_link/3,4,5 for synchronous start.

      Information necessary for a process within a supervision tree, such as │ │ │ │ details on ancestors and the initial call, is stored when a process │ │ │ │ is started through one of these functions.

      If the process terminates with a reason other than normal or shutdown, a │ │ │ │ crash report is generated. For more information about the crash report, see │ │ │ │ Logging in Kernel User's Guide.

      In the example, synchronous start is used. The process starts by calling │ │ │ │ -ch4:start_link():

      start_link() ->
      │ │ │ │ -    proc_lib:start_link(ch4, init, [self()]).

      ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ │ +ch4:start_link():

      start_link() ->
      │ │ │ │ +    proc_lib:start_link(ch4, init, [self()]).

      ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ │ name, a function name, and an argument list as arguments. It then │ │ │ │ spawns a new process and establishes a link. The new process starts │ │ │ │ by executing the given function, here ch4:init(Pid), where Pid is │ │ │ │ the pid of the parent process (obtained by the call to │ │ │ │ self() in the call to proc_lib:start_link/3).

      All initialization, including name registration, is done in init/1. The new │ │ │ │ -process has to acknowledge that it has been started to the parent:

      init(Parent) ->
      │ │ │ │ +process has to acknowledge that it has been started to the parent:

      init(Parent) ->
      │ │ │ │      ...
      │ │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
      │ │ │ │ -    loop(...).

      proc_lib:start_link/3 is synchronous and does not return until │ │ │ │ + proc_lib:init_ack(Parent, {ok, self()}), │ │ │ │ + loop(...).

      proc_lib:start_link/3 is synchronous and does not return until │ │ │ │ proc_lib:init_ack/1,2 or │ │ │ │ proc_lib:init_fail/2,3 has been called, │ │ │ │ or the process has exited.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Debugging │ │ │ │

      │ │ │ │

      To support the debug facilities in sys, a debug structure is needed. The │ │ │ │ -Deb term is initialized using sys:debug_options/1:

      init(Parent) ->
      │ │ │ │ +Deb term is initialized using sys:debug_options/1:

      init(Parent) ->
      │ │ │ │      ...
      │ │ │ │ -    Deb = sys:debug_options([]),
      │ │ │ │ +    Deb = sys:debug_options([]),
      │ │ │ │      ...
      │ │ │ │ -    loop(Chs, Parent, Deb).

      sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ │ + loop(Chs, Parent, Deb).

      sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ │ example means that debugging is initially disabled. For information about the │ │ │ │ possible options, see sys in STDLIB.

      For each system event to be logged or traced, the following function │ │ │ │ -is to be called:

      sys:handle_debug(Deb, Func, Info, Event) => Deb1

      The arguments have the follow meaning:

      • Deb is the debug structure as returned from sys:debug_options/1.
      • Func is a fun specifying a (user-defined) function used to format trace │ │ │ │ +is to be called:

        sys:handle_debug(Deb, Func, Info, Event) => Deb1

        The arguments have the follow meaning:

        • Deb is the debug structure as returned from sys:debug_options/1.
        • Func is a fun specifying a (user-defined) function used to format trace │ │ │ │ output. For each system event, the format function is called as │ │ │ │ Func(Dev, Event, Info), where:
          • Dev is the I/O device to which the output is to be printed. See io │ │ │ │ in STDLIB.
          • Event and Info are passed as-is from the call to sys:handle_debug/4.
        • Info is used to pass more information to Func. It can be any term, and it │ │ │ │ is passed as-is.
        • Event is the system event. It is up to the user to define what a system │ │ │ │ event is and how it is to be represented. Typically, at least incoming and │ │ │ │ outgoing messages are considered system events and represented by the tuples │ │ │ │ {in,Msg[,From]} and {out,Msg,To[,State]}, respectively.

        sys:handle_debug/4 returns an updated debug structure Deb1.

        In the example, sys:handle_debug/4 is called for each incoming and │ │ │ │ outgoing message. The format function Func is the function │ │ │ │ -ch4:write_debug/3, which prints the message using io:format/3.

        loop(Chs, Parent, Deb) ->
        │ │ │ │ +ch4:write_debug/3, which prints the message using io:format/3.

        loop(Chs, Parent, Deb) ->
        │ │ │ │      receive
        │ │ │ │ -        {From, alloc} ->
        │ │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
        │ │ │ │ -                                    ch4, {in, alloc, From}),
        │ │ │ │ -            {Ch, Chs2} = alloc(Chs),
        │ │ │ │ -            From ! {ch4, Ch},
        │ │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
        │ │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
        │ │ │ │ -            loop(Chs2, Parent, Deb3);
        │ │ │ │ -        {free, Ch} ->
        │ │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
        │ │ │ │ -                                    ch4, {in, {free, Ch}}),
        │ │ │ │ -            Chs2 = free(Ch, Chs),
        │ │ │ │ -            loop(Chs2, Parent, Deb2);
        │ │ │ │ +        {From, alloc} ->
        │ │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
        │ │ │ │ +                                    ch4, {in, alloc, From}),
        │ │ │ │ +            {Ch, Chs2} = alloc(Chs),
        │ │ │ │ +            From ! {ch4, Ch},
        │ │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
        │ │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
        │ │ │ │ +            loop(Chs2, Parent, Deb3);
        │ │ │ │ +        {free, Ch} ->
        │ │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
        │ │ │ │ +                                    ch4, {in, {free, Ch}}),
        │ │ │ │ +            Chs2 = free(Ch, Chs),
        │ │ │ │ +            loop(Chs2, Parent, Deb2);
        │ │ │ │          ...
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │ -write_debug(Dev, Event, Name) ->
        │ │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

        │ │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling System Messages │ │ │ │

        │ │ │ │

        System messages are received as:

        {system, From, Request}

        The content and meaning of these messages are not to be interpreted by the │ │ │ │ -process. Instead the following function is to be called:

        sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

        The arguments have the following meaning:

        • Request and From from the received system message are to be │ │ │ │ +process. Instead the following function is to be called:

          sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

          The arguments have the following meaning:

          • Request and From from the received system message are to be │ │ │ │ passed as-is to the call to sys:handle_system_msg/6.
          • Parent is the pid of the parent process.
          • Module is the name of the module implementing the speciall process.
          • Deb is the debug structure.
          • State is a term describing the internal state and is passed on to │ │ │ │ Module:system_continue/3, Module:system_terminate/4/ │ │ │ │ Module:system_get_state/1, and Module:system_replace_state/2.

          sys:handle_system_msg/6 does not return. It handles the system │ │ │ │ message and eventually calls either of the following functions:

          • Module:system_continue(Parent, Deb, State) - if process execution is to │ │ │ │ continue.

          • Module:system_terminate(Reason, Parent, Deb, State) - if the │ │ │ │ process is to terminate.

          While handling the system message, sys:handle_system_msg/6 can call │ │ │ │ one of the following functions:

          • Module:system_get_state(State) - if the process is to return its state.

          • Module:system_replace_state(StateFun, State) - if the process is │ │ │ │ to replace its state using the fun StateFun fun. See sys:replace_state/3 │ │ │ │ for more information.

          • system_code_change(Misc, Module, OldVsn, Extra) - if the process is to │ │ │ │ perform a code change.

          A process in a supervision tree is expected to terminate with the same reason as │ │ │ │ -its parent.

          In the example, system messages are handed by the following code:

          loop(Chs, Parent, Deb) ->
          │ │ │ │ +its parent.

          In the example, system messages are handed by the following code:

          loop(Chs, Parent, Deb) ->
          │ │ │ │      receive
          │ │ │ │          ...
          │ │ │ │  
          │ │ │ │ -        {system, From, Request} ->
          │ │ │ │ -            sys:handle_system_msg(Request, From, Parent,
          │ │ │ │ -                                  ch4, Deb, Chs)
          │ │ │ │ +        {system, From, Request} ->
          │ │ │ │ +            sys:handle_system_msg(Request, From, Parent,
          │ │ │ │ +                                  ch4, Deb, Chs)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -system_continue(Parent, Deb, Chs) ->
          │ │ │ │ -    loop(Chs, Parent, Deb).
          │ │ │ │ +system_continue(Parent, Deb, Chs) ->
          │ │ │ │ +    loop(Chs, Parent, Deb).
          │ │ │ │  
          │ │ │ │ -system_terminate(Reason, Parent, Deb, Chs) ->
          │ │ │ │ -    exit(Reason).
          │ │ │ │ +system_terminate(Reason, Parent, Deb, Chs) ->
          │ │ │ │ +    exit(Reason).
          │ │ │ │  
          │ │ │ │ -system_get_state(Chs) ->
          │ │ │ │ -    {ok, Chs, Chs}.
          │ │ │ │ +system_get_state(Chs) ->
          │ │ │ │ +    {ok, Chs, Chs}.
          │ │ │ │  
          │ │ │ │ -system_replace_state(StateFun, Chs) ->
          │ │ │ │ -    NChs = StateFun(Chs),
          │ │ │ │ -    {ok, NChs, NChs}.

          If a special process is configured to trap exits, it must take notice │ │ │ │ +system_replace_state(StateFun, Chs) -> │ │ │ │ + NChs = StateFun(Chs), │ │ │ │ + {ok, NChs, NChs}.

          If a special process is configured to trap exits, it must take notice │ │ │ │ of 'EXIT' messages from its parent process and terminate using the │ │ │ │ -same exit reason once the parent process has terminated.

          Here is an example:

          init(Parent) ->
          │ │ │ │ +same exit reason once the parent process has terminated.

          Here is an example:

          init(Parent) ->
          │ │ │ │      ...,
          │ │ │ │ -    process_flag(trap_exit, true),
          │ │ │ │ +    process_flag(trap_exit, true),
          │ │ │ │      ...,
          │ │ │ │ -    loop(Parent).
          │ │ │ │ +    loop(Parent).
          │ │ │ │  
          │ │ │ │ -loop(Parent) ->
          │ │ │ │ +loop(Parent) ->
          │ │ │ │      receive
          │ │ │ │          ...
          │ │ │ │ -        {'EXIT', Parent, Reason} ->
          │ │ │ │ +        {'EXIT', Parent, Reason} ->
          │ │ │ │              %% Clean up here, if needed.
          │ │ │ │ -            exit(Reason);
          │ │ │ │ +            exit(Reason);
          │ │ │ │          ...
          │ │ │ │      end.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ User-Defined Behaviours │ │ │ │

          │ │ │ │ @@ -353,69 +353,69 @@ │ │ │ │ function. Note that the -optional_callbacks attribute is to be used together │ │ │ │ with the -callback attribute; it cannot be combined with the │ │ │ │ behaviour_info() function described below.

          Tools that need to know about optional callback functions can call │ │ │ │ Behaviour:behaviour_info(optional_callbacks) to get a list of all optional │ │ │ │ callback functions.

          Note

          We recommend using the -callback attribute rather than the │ │ │ │ behaviour_info() function. The reason is that the extra type information can │ │ │ │ be used by tools to produce documentation or find discrepancies.

          As an alternative to the -callback and -optional_callbacks attributes you │ │ │ │ -may directly implement and export behaviour_info():

          behaviour_info(callbacks) ->
          │ │ │ │ -    [{Name1, Arity1},...,{NameN, ArityN}].

          where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ │ +may directly implement and export behaviour_info():

          behaviour_info(callbacks) ->
          │ │ │ │ +    [{Name1, Arity1},...,{NameN, ArityN}].

          where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ │ This function is otherwise automatically generated by the compiler using the │ │ │ │ -callback attributes.

          When the compiler encounters the module attribute -behaviour(Behaviour). in a │ │ │ │ module Mod, it calls Behaviour:behaviour_info(callbacks) and compares the │ │ │ │ result with the set of functions actually exported from Mod, and issues a │ │ │ │ warning if any callback function is missing.

          Example:

          %% User-defined behaviour module
          │ │ │ │ --module(simple_server).
          │ │ │ │ --export([start_link/2, init/3, ...]).
          │ │ │ │ +-module(simple_server).
          │ │ │ │ +-export([start_link/2, init/3, ...]).
          │ │ │ │  
          │ │ │ │ --callback init(State :: term()) -> 'ok'.
          │ │ │ │ --callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
          │ │ │ │ --callback terminate() -> 'ok'.
          │ │ │ │ --callback format_state(State :: term()) -> term().
          │ │ │ │ +-callback init(State :: term()) -> 'ok'.
          │ │ │ │ +-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
          │ │ │ │ +-callback terminate() -> 'ok'.
          │ │ │ │ +-callback format_state(State :: term()) -> term().
          │ │ │ │  
          │ │ │ │ --optional_callbacks([format_state/1]).
          │ │ │ │ +-optional_callbacks([format_state/1]).
          │ │ │ │  
          │ │ │ │  %% Alternatively you may define:
          │ │ │ │  %%
          │ │ │ │  %% -export([behaviour_info/1]).
          │ │ │ │  %% behaviour_info(callbacks) ->
          │ │ │ │  %%     [{init,1},
          │ │ │ │  %%      {handle_req,2},
          │ │ │ │  %%      {terminate,0}].
          │ │ │ │  
          │ │ │ │ -start_link(Name, Module) ->
          │ │ │ │ -    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
          │ │ │ │ +start_link(Name, Module) ->
          │ │ │ │ +    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
          │ │ │ │  
          │ │ │ │ -init(Parent, Name, Module) ->
          │ │ │ │ -    register(Name, self()),
          │ │ │ │ +init(Parent, Name, Module) ->
          │ │ │ │ +    register(Name, self()),
          │ │ │ │      ...,
          │ │ │ │ -    Dbg = sys:debug_options([]),
          │ │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
          │ │ │ │ -    loop(Parent, Module, Deb, ...).
          │ │ │ │ +    Dbg = sys:debug_options([]),
          │ │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
          │ │ │ │ +    loop(Parent, Module, Deb, ...).
          │ │ │ │  
          │ │ │ │ -...

          In a callback module:

          -module(db).
          │ │ │ │ --behaviour(simple_server).
          │ │ │ │ +...

          In a callback module:

          -module(db).
          │ │ │ │ +-behaviour(simple_server).
          │ │ │ │  
          │ │ │ │ --export([init/1, handle_req/2, terminate/0]).
          │ │ │ │ +-export([init/1, handle_req/2, terminate/0]).
          │ │ │ │  
          │ │ │ │  ...

          The contracts specified with -callback attributes in behaviour modules can be │ │ │ │ further refined by adding -spec attributes in callback modules. This can be │ │ │ │ useful as -callback contracts are usually generic. The same callback module │ │ │ │ -with contracts for the callbacks:

          -module(db).
          │ │ │ │ --behaviour(simple_server).
          │ │ │ │ +with contracts for the callbacks:

          -module(db).
          │ │ │ │ +-behaviour(simple_server).
          │ │ │ │  
          │ │ │ │ --export([init/1, handle_req/2, terminate/0]).
          │ │ │ │ +-export([init/1, handle_req/2, terminate/0]).
          │ │ │ │  
          │ │ │ │ --record(state, {field1 :: [atom()], field2 :: integer()}).
          │ │ │ │ +-record(state, {field1 :: [atom()], field2 :: integer()}).
          │ │ │ │  
          │ │ │ │ --type state()   :: #state{}.
          │ │ │ │ --type request() :: {'store', term(), term()};
          │ │ │ │ -                   {'lookup', term()}.
          │ │ │ │ +-type state()   :: #state{}.
          │ │ │ │ +-type request() :: {'store', term(), term()};
          │ │ │ │ +                   {'lookup', term()}.
          │ │ │ │  
          │ │ │ │  ...
          │ │ │ │  
          │ │ │ │ --spec handle_req(request(), state()) -> {'ok', term()}.
          │ │ │ │ +-spec handle_req(request(), state()) -> {'ok', term()}.
          │ │ │ │  
          │ │ │ │  ...

          Each -spec contract is to be a subtype of the respective -callback contract.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/seq_prog.xhtml │ │ │ │ @@ -41,293 +41,293 @@ │ │ │ │
          7 │ │ │ │ 2>

          As shown, the Erlang shell numbers the lines that can be entered, (as 1> 2>) and │ │ │ │ that it correctly says that 2 + 5 is 7. If you make writing mistakes in the │ │ │ │ shell, you can delete with the backspace key, as in most shells. There are many │ │ │ │ more editing commands in the shell (see │ │ │ │ tty - A command line interface in ERTS User's Guide).

          (Notice that many line numbers given by the shell in the following examples are │ │ │ │ out of sequence. This is because this tutorial was written and code-tested in │ │ │ │ -separate sessions).

          Here is a bit more complex calculation:

          2> (42 + 77) * 66 / 3.
          │ │ │ │ +separate sessions).

          Here is a bit more complex calculation:

          2> (42 + 77) * 66 / 3.
          │ │ │ │  2618.0

          Notice the use of brackets, the multiplication operator *, and the division │ │ │ │ operator /, as in normal arithmetic (see │ │ │ │ Expressions).

          Press Control-C to shut down the Erlang system and the Erlang shell.

          The following output is shown:

          BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
          │ │ │ │         (v)ersion (k)ill (D)b-tables (d)istribution
          │ │ │ │  a
          │ │ │ │ -$

          Type a to leave the Erlang system.

          Another way to shut down the Erlang system is by entering halt/0:

          3> halt().
          │ │ │ │ +$

          Type a to leave the Erlang system.

          Another way to shut down the Erlang system is by entering halt/0:

          3> halt().
          │ │ │ │  $

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Modules and Functions │ │ │ │

          │ │ │ │

          A programming language is not much use if you only can run code from the shell. │ │ │ │ So here is a small Erlang program. Enter it into a file named tut.erl using a │ │ │ │ suitable text editor. The file name tut.erl is important, and also that it is │ │ │ │ in the same directory as the one where you started erl). If you are lucky your │ │ │ │ editor has an Erlang mode that makes it easier for you to enter and format your │ │ │ │ code nicely (see The Erlang mode for Emacs │ │ │ │ in Tools User's Guide), but you can manage perfectly well without. Here is the │ │ │ │ -code to enter:

          -module(tut).
          │ │ │ │ --export([double/1]).
          │ │ │ │ +code to enter:

          -module(tut).
          │ │ │ │ +-export([double/1]).
          │ │ │ │  
          │ │ │ │ -double(X) ->
          │ │ │ │ +double(X) ->
          │ │ │ │      2 * X.

          It is not hard to guess that this program doubles the value of numbers. The │ │ │ │ first two lines of the code are described later. Let us compile the program. │ │ │ │ -This can be done in an Erlang shell as follows, where c means compile:

          3> c(tut).
          │ │ │ │ -{ok,tut}

          The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ │ +This can be done in an Erlang shell as follows, where c means compile:

          3> c(tut).
          │ │ │ │ +{ok,tut}

          The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ │ that there is some mistake in the text that you entered. Additional error │ │ │ │ messages gives an idea to what is wrong so you can modify the text and then try │ │ │ │ -to compile the program again.

          Now run the program:

          4> tut:double(10).
          │ │ │ │ +to compile the program again.

          Now run the program:

          4> tut:double(10).
          │ │ │ │  20

          As expected, double of 10 is 20.

          Now let us get back to the first two lines of the code. Erlang programs are │ │ │ │ written in files. Each file contains an Erlang module. The first line of code │ │ │ │ -in the module is the module name (see Modules):

          -module(tut).

          Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ │ +in the module is the module name (see Modules):

          -module(tut).

          Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ │ line. The files which are used to store the module must have the same name as │ │ │ │ the module but with the extension .erl. In this case the file name is │ │ │ │ tut.erl. When using a function in another module, the syntax │ │ │ │ module_name:function_name(arguments) is used. So the following means call │ │ │ │ -function double in module tut with argument 10.

          4> tut:double(10).

          The second line says that the module tut contains a function called double, │ │ │ │ -which takes one argument (X in our example):

          -export([double/1]).

          The second line also says that this function can be called from outside the │ │ │ │ +function double in module tut with argument 10.

          4> tut:double(10).

          The second line says that the module tut contains a function called double, │ │ │ │ +which takes one argument (X in our example):

          -export([double/1]).

          The second line also says that this function can be called from outside the │ │ │ │ module tut. More about this later. Again, notice the . at the end of the │ │ │ │ line.

          Now for a more complicated example, the factorial of a number. For example, the │ │ │ │ -factorial of 4 is 4 3 2 * 1, which equals 24.

          Enter the following code in a file named tut1.erl:

          -module(tut1).
          │ │ │ │ --export([fac/1]).
          │ │ │ │ +factorial of 4 is 4  3  2 * 1, which equals 24.

          Enter the following code in a file named tut1.erl:

          -module(tut1).
          │ │ │ │ +-export([fac/1]).
          │ │ │ │  
          │ │ │ │ -fac(1) ->
          │ │ │ │ +fac(1) ->
          │ │ │ │      1;
          │ │ │ │ -fac(N) ->
          │ │ │ │ -    N * fac(N - 1).

          So this is a module, called tut1 that contains a function called fac>, which │ │ │ │ -takes one argument, N.

          The first part says that the factorial of 1 is 1.:

          fac(1) ->
          │ │ │ │ +fac(N) ->
          │ │ │ │ +    N * fac(N - 1).

          So this is a module, called tut1 that contains a function called fac>, which │ │ │ │ +takes one argument, N.

          The first part says that the factorial of 1 is 1.:

          fac(1) ->
          │ │ │ │      1;

          Notice that this part ends with a semicolon ; that indicates that there is │ │ │ │ more of the function fac> to come.

          The second part says that the factorial of N is N multiplied by the factorial of │ │ │ │ -N - 1:

          fac(N) ->
          │ │ │ │ -    N * fac(N - 1).

          Notice that this part ends with a . saying that there are no more parts of │ │ │ │ -this function.

          Compile the file:

          5> c(tut1).
          │ │ │ │ -{ok,tut1}

          And now calculate the factorial of 4.

          6> tut1:fac(4).
          │ │ │ │ +N - 1:

          fac(N) ->
          │ │ │ │ +    N * fac(N - 1).

          Notice that this part ends with a . saying that there are no more parts of │ │ │ │ +this function.

          Compile the file:

          5> c(tut1).
          │ │ │ │ +{ok,tut1}

          And now calculate the factorial of 4.

          6> tut1:fac(4).
          │ │ │ │  24

          Here the function fac> in module tut1 is called with argument 4.

          A function can have many arguments. Let us expand the module tut1 with the │ │ │ │ -function to multiply two numbers:

          -module(tut1).
          │ │ │ │ --export([fac/1, mult/2]).
          │ │ │ │ +function to multiply two numbers:

          -module(tut1).
          │ │ │ │ +-export([fac/1, mult/2]).
          │ │ │ │  
          │ │ │ │ -fac(1) ->
          │ │ │ │ +fac(1) ->
          │ │ │ │      1;
          │ │ │ │ -fac(N) ->
          │ │ │ │ -    N * fac(N - 1).
          │ │ │ │ +fac(N) ->
          │ │ │ │ +    N * fac(N - 1).
          │ │ │ │  
          │ │ │ │ -mult(X, Y) ->
          │ │ │ │ +mult(X, Y) ->
          │ │ │ │      X * Y.

          Notice that it is also required to expand the -export line with the │ │ │ │ -information that there is another function mult with two arguments.

          Compile:

          7> c(tut1).
          │ │ │ │ -{ok,tut1}

          Try out the new function mult:

          8> tut1:mult(3,4).
          │ │ │ │ +information that there is another function mult with two arguments.

          Compile:

          7> c(tut1).
          │ │ │ │ +{ok,tut1}

          Try out the new function mult:

          8> tut1:mult(3,4).
          │ │ │ │  12

          In this example the numbers are integers and the arguments in the functions in │ │ │ │ the code N, X, and Y are called variables. Variables must start with a │ │ │ │ capital letter (see Variables). Examples of │ │ │ │ variables are Number, ShoeSize, and Age.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Atoms │ │ │ │

          │ │ │ │

          Atom is another data type in Erlang. Atoms start with a small letter (see │ │ │ │ Atom), for example, charles, centimeter, and │ │ │ │ inch. Atoms are simply names, nothing else. They are not like variables, which │ │ │ │ can have a value.

          Enter the next program in a file named tut2.erl). It can be useful for │ │ │ │ -converting from inches to centimeters and conversely:

          -module(tut2).
          │ │ │ │ --export([convert/2]).
          │ │ │ │ +converting from inches to centimeters and conversely:

          -module(tut2).
          │ │ │ │ +-export([convert/2]).
          │ │ │ │  
          │ │ │ │ -convert(M, inch) ->
          │ │ │ │ +convert(M, inch) ->
          │ │ │ │      M / 2.54;
          │ │ │ │  
          │ │ │ │ -convert(N, centimeter) ->
          │ │ │ │ -    N * 2.54.

          Compile:

          9> c(tut2).
          │ │ │ │ -{ok,tut2}

          Test:

          10> tut2:convert(3, inch).
          │ │ │ │ +convert(N, centimeter) ->
          │ │ │ │ +    N * 2.54.

          Compile:

          9> c(tut2).
          │ │ │ │ +{ok,tut2}

          Test:

          10> tut2:convert(3, inch).
          │ │ │ │  1.1811023622047243
          │ │ │ │ -11> tut2:convert(7, centimeter).
          │ │ │ │ +11> tut2:convert(7, centimeter).
          │ │ │ │  17.78

          Notice the introduction of decimals (floating point numbers) without any │ │ │ │ explanation. Hopefully you can cope with that.

          Let us see what happens if something other than centimeter or inch is │ │ │ │ -entered in the convert function:

          12> tut2:convert(3, miles).
          │ │ │ │ +entered in the convert function:

          12> tut2:convert(3, miles).
          │ │ │ │  ** exception error: no function clause matching tut2:convert(3,miles) (tut2.erl, line 4)

          The two parts of the convert function are called its clauses. As shown, │ │ │ │ miles is not part of either of the clauses. The Erlang system cannot match │ │ │ │ either of the clauses so an error message function_clause is returned. The │ │ │ │ shell formats the error message nicely, but the error tuple is saved in the │ │ │ │ -shell's history list and can be output by the shell command v/1:

          13> v(12).
          │ │ │ │ -{'EXIT',{function_clause,[{tut2,convert,
          │ │ │ │ -                                [3,miles],
          │ │ │ │ -                                [{file,"tut2.erl"},{line,4}]},
          │ │ │ │ -                          {erl_eval,do_apply,6,
          │ │ │ │ -                                    [{file,"erl_eval.erl"},{line,677}]},
          │ │ │ │ -                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
          │ │ │ │ -                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
          │ │ │ │ -                          {shell,eval_loop,3,
          │ │ │ │ -                                 [{file,"shell.erl"},{line,627}]}]}}

          │ │ │ │ +shell's history list and can be output by the shell command v/1:

          13> v(12).
          │ │ │ │ +{'EXIT',{function_clause,[{tut2,convert,
          │ │ │ │ +                                [3,miles],
          │ │ │ │ +                                [{file,"tut2.erl"},{line,4}]},
          │ │ │ │ +                          {erl_eval,do_apply,6,
          │ │ │ │ +                                    [{file,"erl_eval.erl"},{line,677}]},
          │ │ │ │ +                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
          │ │ │ │ +                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
          │ │ │ │ +                          {shell,eval_loop,3,
          │ │ │ │ +                                 [{file,"shell.erl"},{line,627}]}]}}

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tuples │ │ │ │

          │ │ │ │ -

          Now the tut2 program is hardly good programming style. Consider:

          tut2:convert(3, inch).

          Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ │ +

          Now the tut2 program is hardly good programming style. Consider:

          tut2:convert(3, inch).

          Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ │ is to be converted to inches? Erlang has a way to group things together to make │ │ │ │ things more understandable. These are called tuples and are surrounded by │ │ │ │ curly brackets, { and }.

          So, {inch,3} denotes 3 inches and {centimeter,5} denotes 5 centimeters. Now │ │ │ │ let us write a new program that converts centimeters to inches and conversely. │ │ │ │ -Enter the following code in a file called tut3.erl):

          -module(tut3).
          │ │ │ │ --export([convert_length/1]).
          │ │ │ │ +Enter the following code in a file called tut3.erl):

          -module(tut3).
          │ │ │ │ +-export([convert_length/1]).
          │ │ │ │  
          │ │ │ │ -convert_length({centimeter, X}) ->
          │ │ │ │ -    {inch, X / 2.54};
          │ │ │ │ -convert_length({inch, Y}) ->
          │ │ │ │ -    {centimeter, Y * 2.54}.

          Compile and test:

          14> c(tut3).
          │ │ │ │ -{ok,tut3}
          │ │ │ │ -15> tut3:convert_length({inch, 5}).
          │ │ │ │ -{centimeter,12.7}
          │ │ │ │ -16> tut3:convert_length(tut3:convert_length({inch, 5})).
          │ │ │ │ -{inch,5.0}

          Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ │ +convert_length({centimeter, X}) -> │ │ │ │ + {inch, X / 2.54}; │ │ │ │ +convert_length({inch, Y}) -> │ │ │ │ + {centimeter, Y * 2.54}.

          Compile and test:

          14> c(tut3).
          │ │ │ │ +{ok,tut3}
          │ │ │ │ +15> tut3:convert_length({inch, 5}).
          │ │ │ │ +{centimeter,12.7}
          │ │ │ │ +16> tut3:convert_length(tut3:convert_length({inch, 5})).
          │ │ │ │ +{inch,5.0}

          Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ │ reassuringly get back to the original value. That is, the argument to a function │ │ │ │ can be the result of another function. Consider how line 16 (above) works. The │ │ │ │ argument given to the function {inch,5} is first matched against the first │ │ │ │ head clause of convert_length, that is, convert_length({centimeter,X}). It │ │ │ │ can be seen that {centimeter,X} does not match {inch,5} (the head is the bit │ │ │ │ before the ->). This having failed, let us try the head of the next clause │ │ │ │ that is, convert_length({inch,Y}). This matches, and Y gets the value 5.

          Tuples can have more than two parts, in fact as many parts as you want, and │ │ │ │ contain any valid Erlang term. For example, to represent the temperature of │ │ │ │ -various cities of the world:

          {moscow, {c, -10}}
          │ │ │ │ -{cape_town, {f, 70}}
          │ │ │ │ -{paris, {f, 28}}

          Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ │ +various cities of the world:

          {moscow, {c, -10}}
          │ │ │ │ +{cape_town, {f, 70}}
          │ │ │ │ +{paris, {f, 28}}

          Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ │ element. In the tuple {moscow,{c,-10}}, element 1 is moscow and element 2 │ │ │ │ is {c,-10}. Here c represents Celsius and f Fahrenheit.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Lists │ │ │ │

          │ │ │ │

          Whereas tuples group things together, it is also needed to represent lists of │ │ │ │ things. Lists in Erlang are surrounded by square brackets, [ and ]. For │ │ │ │ -example, a list of the temperatures of various cities in the world can be:

          [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
          │ │ │ │ - {paris, {f, 28}}, {london, {f, 36}}]

          Notice that this list was so long that it did not fit on one line. This does not │ │ │ │ +example, a list of the temperatures of various cities in the world can be:

          [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
          │ │ │ │ + {paris, {f, 28}}, {london, {f, 36}}]

          Notice that this list was so long that it did not fit on one line. This does not │ │ │ │ matter, Erlang allows line breaks at all "sensible places" but not, for example, │ │ │ │ in the middle of atoms, integers, and others.

          A useful way of looking at parts of lists, is by using |. This is best │ │ │ │ -explained by an example using the shell:

          17> [First |TheRest] = [1,2,3,4,5].
          │ │ │ │ -[1,2,3,4,5]
          │ │ │ │ +explained by an example using the shell:

          17> [First |TheRest] = [1,2,3,4,5].
          │ │ │ │ +[1,2,3,4,5]
          │ │ │ │  18> First.
          │ │ │ │  1
          │ │ │ │  19> TheRest.
          │ │ │ │ -[2,3,4,5]

          To separate the first elements of the list from the rest of the list, | is │ │ │ │ -used. First has got value 1 and TheRest has got the value [2,3,4,5].

          Another example:

          20> [E1, E2 | R] = [1,2,3,4,5,6,7].
          │ │ │ │ -[1,2,3,4,5,6,7]
          │ │ │ │ +[2,3,4,5]

          To separate the first elements of the list from the rest of the list, | is │ │ │ │ +used. First has got value 1 and TheRest has got the value [2,3,4,5].

          Another example:

          20> [E1, E2 | R] = [1,2,3,4,5,6,7].
          │ │ │ │ +[1,2,3,4,5,6,7]
          │ │ │ │  21> E1.
          │ │ │ │  1
          │ │ │ │  22> E2.
          │ │ │ │  2
          │ │ │ │  23> R.
          │ │ │ │ -[3,4,5,6,7]

          Here you see the use of | to get the first two elements from the list. If you │ │ │ │ +[3,4,5,6,7]

          Here you see the use of | to get the first two elements from the list. If you │ │ │ │ try to get more elements from the list than there are elements in the list, an │ │ │ │ error is returned. Notice also the special case of the list with no elements, │ │ │ │ -[]:

          24> [A, B | C] = [1, 2].
          │ │ │ │ -[1,2]
          │ │ │ │ +[]:

          24> [A, B | C] = [1, 2].
          │ │ │ │ +[1,2]
          │ │ │ │  25> A.
          │ │ │ │  1
          │ │ │ │  26> B.
          │ │ │ │  2
          │ │ │ │  27> C.
          │ │ │ │ -[]

          In the previous examples, new variable names are used, instead of reusing the │ │ │ │ +[]

          In the previous examples, new variable names are used, instead of reusing the │ │ │ │ old ones: First, TheRest, E1, E2, R, A, B, and C. The reason for │ │ │ │ this is that a variable can only be given a value once in its context (scope). │ │ │ │ More about this later.

          The following example shows how to find the length of a list. Enter the │ │ │ │ -following code in a file named tut4.erl:

          -module(tut4).
          │ │ │ │ +following code in a file named tut4.erl:

          -module(tut4).
          │ │ │ │  
          │ │ │ │ --export([list_length/1]).
          │ │ │ │ +-export([list_length/1]).
          │ │ │ │  
          │ │ │ │ -list_length([]) ->
          │ │ │ │ +list_length([]) ->
          │ │ │ │      0;
          │ │ │ │ -list_length([First | Rest]) ->
          │ │ │ │ -    1 + list_length(Rest).

          Compile and test:

          28> c(tut4).
          │ │ │ │ -{ok,tut4}
          │ │ │ │ -29> tut4:list_length([1,2,3,4,5,6,7]).
          │ │ │ │ -7

          Explanation:

          list_length([]) ->
          │ │ │ │ -    0;

          The length of an empty list is obviously 0.

          list_length([First | Rest]) ->
          │ │ │ │ -    1 + list_length(Rest).

          The length of a list with the first element First and the remaining elements │ │ │ │ +list_length([First | Rest]) -> │ │ │ │ + 1 + list_length(Rest).

          Compile and test:

          28> c(tut4).
          │ │ │ │ +{ok,tut4}
          │ │ │ │ +29> tut4:list_length([1,2,3,4,5,6,7]).
          │ │ │ │ +7

          Explanation:

          list_length([]) ->
          │ │ │ │ +    0;

          The length of an empty list is obviously 0.

          list_length([First | Rest]) ->
          │ │ │ │ +    1 + list_length(Rest).

          The length of a list with the first element First and the remaining elements │ │ │ │ Rest is 1 + the length of Rest.

          (Advanced readers only: This is not tail recursive, there is a better way to │ │ │ │ write this function.)

          In general, tuples are used where "records" or "structs" are used in other │ │ │ │ languages. Also, lists are used when representing things with varying sizes, │ │ │ │ that is, where linked lists are used in other languages.

          Erlang does not have a string data type. Instead, strings can be represented by │ │ │ │ lists of Unicode characters. This implies for example that the list [97,98,99] │ │ │ │ is equivalent to "abc". The Erlang shell is "clever" and guesses what list you │ │ │ │ -mean and outputs it in what it thinks is the most appropriate form, for example:

          30> [97,98,99].
          │ │ │ │ +mean and outputs it in what it thinks is the most appropriate form, for example:

          30> [97,98,99].
          │ │ │ │  "abc"

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps │ │ │ │

          │ │ │ │

          Maps are a set of key to value associations. These associations are encapsulated │ │ │ │ -with #{ and }. To create an association from "key" to value 42:

          > #{ "key" => 42 }.
          │ │ │ │ -#{"key" => 42}

          Let us jump straight into the deep end with an example using some interesting │ │ │ │ +with #{ and }. To create an association from "key" to value 42:

          > #{ "key" => 42 }.
          │ │ │ │ +#{"key" => 42}

          Let us jump straight into the deep end with an example using some interesting │ │ │ │ features.

          The following example shows how to calculate alpha blending using maps to │ │ │ │ -reference color and alpha channels. Enter the code in a file named color.erl):

          -module(color).
          │ │ │ │ +reference color and alpha channels. Enter the code in a file named color.erl):

          -module(color).
          │ │ │ │  
          │ │ │ │ --export([new/4, blend/2]).
          │ │ │ │ +-export([new/4, blend/2]).
          │ │ │ │  
          │ │ │ │ --define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
          │ │ │ │ +-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
          │ │ │ │  
          │ │ │ │ -new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
          │ │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
          │ │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.
          │ │ │ │ -
          │ │ │ │ -blend(Src,Dst) ->
          │ │ │ │ -    blend(Src,Dst,alpha(Src,Dst)).
          │ │ │ │ -
          │ │ │ │ -blend(Src,Dst,Alpha) when Alpha > 0.0 ->
          │ │ │ │ -    Dst#{
          │ │ │ │ -        red   := red(Src,Dst) / Alpha,
          │ │ │ │ -        green := green(Src,Dst) / Alpha,
          │ │ │ │ -        blue  := blue(Src,Dst) / Alpha,
          │ │ │ │ +new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
          │ │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
          │ │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.
          │ │ │ │ +
          │ │ │ │ +blend(Src,Dst) ->
          │ │ │ │ +    blend(Src,Dst,alpha(Src,Dst)).
          │ │ │ │ +
          │ │ │ │ +blend(Src,Dst,Alpha) when Alpha > 0.0 ->
          │ │ │ │ +    Dst#{
          │ │ │ │ +        red   := red(Src,Dst) / Alpha,
          │ │ │ │ +        green := green(Src,Dst) / Alpha,
          │ │ │ │ +        blue  := blue(Src,Dst) / Alpha,
          │ │ │ │          alpha := Alpha
          │ │ │ │ -    };
          │ │ │ │ -blend(_,Dst,_) ->
          │ │ │ │ -    Dst#{
          │ │ │ │ +    };
          │ │ │ │ +blend(_,Dst,_) ->
          │ │ │ │ +    Dst#{
          │ │ │ │          red   := 0.0,
          │ │ │ │          green := 0.0,
          │ │ │ │          blue  := 0.0,
          │ │ │ │          alpha := 0.0
          │ │ │ │ -    }.
          │ │ │ │ +    }.
          │ │ │ │  
          │ │ │ │ -alpha(#{alpha := SA}, #{alpha := DA}) ->
          │ │ │ │ -    SA + DA*(1.0 - SA).
          │ │ │ │ +alpha(#{alpha := SA}, #{alpha := DA}) ->
          │ │ │ │ +    SA + DA*(1.0 - SA).
          │ │ │ │  
          │ │ │ │ -red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
          │ │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
          │ │ │ │ -green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) ->
          │ │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
          │ │ │ │ -blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
          │ │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

          Compile and test:

          > c(color).
          │ │ │ │ -{ok,color}
          │ │ │ │ -> C1 = color:new(0.3,0.4,0.5,1.0).
          │ │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
          │ │ │ │ -> C2 = color:new(1.0,0.8,0.1,0.3).
          │ │ │ │ -#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
          │ │ │ │ -> color:blend(C1,C2).
          │ │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
          │ │ │ │ -> color:blend(C2,C1).
          │ │ │ │ -#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

          This example warrants some explanation:

          -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

          First a macro is_channel is defined to help with the guard tests. This is only │ │ │ │ +red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) -> │ │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ │ +green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) -> │ │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ │ +blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) -> │ │ │ │ + SV*SA + DV*DA*(1.0 - SA).

          Compile and test:

          > c(color).
          │ │ │ │ +{ok,color}
          │ │ │ │ +> C1 = color:new(0.3,0.4,0.5,1.0).
          │ │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
          │ │ │ │ +> C2 = color:new(1.0,0.8,0.1,0.3).
          │ │ │ │ +#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
          │ │ │ │ +> color:blend(C1,C2).
          │ │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
          │ │ │ │ +> color:blend(C2,C1).
          │ │ │ │ +#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

          This example warrants some explanation:

          -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

          First a macro is_channel is defined to help with the guard tests. This is only │ │ │ │ here for convenience and to reduce syntax cluttering. For more information about │ │ │ │ -macros, see The Preprocessor.

          new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
          │ │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
          │ │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.

          The function new/4 creates a new map term and lets the keys red, green, │ │ │ │ +macros, see The Preprocessor.

          new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
          │ │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
          │ │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.

          The function new/4 creates a new map term and lets the keys red, green, │ │ │ │ blue, and alpha be associated with an initial value. In this case, only │ │ │ │ float values between and including 0.0 and 1.0 are allowed, as ensured by the │ │ │ │ ?is_channel/1 macro for each argument. Only the => operator is allowed when │ │ │ │ creating a new map.

          By calling blend/2 on any color term created by new/4, the resulting color │ │ │ │ -can be calculated as determined by the two map terms.

          The first thing blend/2 does is to calculate the resulting alpha channel:

          alpha(#{alpha := SA}, #{alpha := DA}) ->
          │ │ │ │ -    SA + DA*(1.0 - SA).

          The value associated with key alpha is fetched for both arguments using the │ │ │ │ +can be calculated as determined by the two map terms.

          The first thing blend/2 does is to calculate the resulting alpha channel:

          alpha(#{alpha := SA}, #{alpha := DA}) ->
          │ │ │ │ +    SA + DA*(1.0 - SA).

          The value associated with key alpha is fetched for both arguments using the │ │ │ │ := operator. The other keys in the map are ignored, only the key alpha is │ │ │ │ -required and checked for.

          This is also the case for functions red/2, blue/2, and green/2.

          red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
          │ │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

          The difference here is that a check is made for two keys in each map argument. │ │ │ │ -The other keys are ignored.

          Finally, let us return the resulting color in blend/3:

          blend(Src,Dst,Alpha) when Alpha > 0.0 ->
          │ │ │ │ -    Dst#{
          │ │ │ │ -        red   := red(Src,Dst) / Alpha,
          │ │ │ │ -        green := green(Src,Dst) / Alpha,
          │ │ │ │ -        blue  := blue(Src,Dst) / Alpha,
          │ │ │ │ +required and checked for.

          This is also the case for functions red/2, blue/2, and green/2.

          red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
          │ │ │ │ +    SV*SA + DV*DA*(1.0 - SA).

          The difference here is that a check is made for two keys in each map argument. │ │ │ │ +The other keys are ignored.

          Finally, let us return the resulting color in blend/3:

          blend(Src,Dst,Alpha) when Alpha > 0.0 ->
          │ │ │ │ +    Dst#{
          │ │ │ │ +        red   := red(Src,Dst) / Alpha,
          │ │ │ │ +        green := green(Src,Dst) / Alpha,
          │ │ │ │ +        blue  := blue(Src,Dst) / Alpha,
          │ │ │ │          alpha := Alpha
          │ │ │ │ -    };

          The Dst map is updated with new channel values. The syntax for updating an │ │ │ │ + };

          The Dst map is updated with new channel values. The syntax for updating an │ │ │ │ existing key with a new value is with the := operator.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standard Modules and Manual Pages │ │ │ │

          │ │ │ │

          Erlang has many standard modules to help you do things. For example, the module │ │ │ │ @@ -347,24 +347,24 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Writing Output to a Terminal │ │ │ │ │ │ │ │

          It is nice to be able to do formatted output in examples, so the next example │ │ │ │ shows a simple way to use the io:format/2 function. Like all other exported │ │ │ │ -functions, you can test the io:format/2 function in the shell:

          31> io:format("hello world~n", []).
          │ │ │ │ +functions, you can test the io:format/2 function in the shell:

          31> io:format("hello world~n", []).
          │ │ │ │  hello world
          │ │ │ │  ok
          │ │ │ │ -32> io:format("this outputs one Erlang term: ~w~n", [hello]).
          │ │ │ │ +32> io:format("this outputs one Erlang term: ~w~n", [hello]).
          │ │ │ │  this outputs one Erlang term: hello
          │ │ │ │  ok
          │ │ │ │ -33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
          │ │ │ │ +33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
          │ │ │ │  this outputs two Erlang terms: helloworld
          │ │ │ │  ok
          │ │ │ │ -34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
          │ │ │ │ +34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
          │ │ │ │  this outputs two Erlang terms: hello world
          │ │ │ │  ok

          The function io:format/2 (that is, format with two arguments) takes two lists. │ │ │ │ The first one is nearly always a list written between " ". This list is printed │ │ │ │ out as it is, except that each ~w is replaced by a term taken in order from the │ │ │ │ second list. Each ~n is replaced by a new line. The io:format/2 function │ │ │ │ itself returns the atom ok if everything goes as planned. Like other functions │ │ │ │ in Erlang, it crashes if an error occurs. This is not a fault in Erlang, it is a │ │ │ │ @@ -378,34 +378,34 @@ │ │ │ │ A Larger Example │ │ │ │ │ │ │ │

          Now for a larger example to consolidate what you have learnt so far. Assume that │ │ │ │ you have a list of temperature readings from a number of cities in the world. │ │ │ │ Some of them are in Celsius and some in Fahrenheit (as in the previous list). │ │ │ │ First let us convert them all to Celsius, then let us print the data neatly.

          %% This module is in file tut5.erl
          │ │ │ │  
          │ │ │ │ --module(tut5).
          │ │ │ │ --export([format_temps/1]).
          │ │ │ │ +-module(tut5).
          │ │ │ │ +-export([format_temps/1]).
          │ │ │ │  
          │ │ │ │  %% Only this function is exported
          │ │ │ │ -format_temps([])->                        % No output for an empty list
          │ │ │ │ +format_temps([])->                        % No output for an empty list
          │ │ │ │      ok;
          │ │ │ │ -format_temps([City | Rest]) ->
          │ │ │ │ -    print_temp(convert_to_celsius(City)),
          │ │ │ │ -    format_temps(Rest).
          │ │ │ │ -
          │ │ │ │ -convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
          │ │ │ │ -    {Name, {c, Temp}};
          │ │ │ │ -convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
          │ │ │ │ -    {Name, {c, (Temp - 32) * 5 / 9}}.
          │ │ │ │ -
          │ │ │ │ -print_temp({Name, {c, Temp}}) ->
          │ │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]).
          35> c(tut5).
          │ │ │ │ -{ok,tut5}
          │ │ │ │ -36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +format_temps([City | Rest]) ->
          │ │ │ │ +    print_temp(convert_to_celsius(City)),
          │ │ │ │ +    format_temps(Rest).
          │ │ │ │ +
          │ │ │ │ +convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
          │ │ │ │ +    {Name, {c, Temp}};
          │ │ │ │ +convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
          │ │ │ │ +    {Name, {c, (Temp - 32) * 5 / 9}}.
          │ │ │ │ +
          │ │ │ │ +print_temp({Name, {c, Temp}}) ->
          │ │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]).
          35> c(tut5).
          │ │ │ │ +{ok,tut5}
          │ │ │ │ +36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │  moscow          -10 c
          │ │ │ │  cape_town       21.11111111111111 c
          │ │ │ │  stockholm       -4 c
          │ │ │ │  paris           -2.2222222222222223 c
          │ │ │ │  london          2.2222222222222223 c
          │ │ │ │  ok

          Before looking at how this program works, notice that a few comments are added │ │ │ │ to the code. A comment starts with a %-character and goes on to the end of the │ │ │ │ @@ -433,28 +433,28 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Matching, Guards, and Scope of Variables │ │ │ │ │ │ │ │

          It can be useful to find the maximum and minimum temperature in lists like this. │ │ │ │ Before extending the program to do this, let us look at functions for finding │ │ │ │ -the maximum value of the elements in a list:

          -module(tut6).
          │ │ │ │ --export([list_max/1]).
          │ │ │ │ +the maximum value of the elements in a list:

          -module(tut6).
          │ │ │ │ +-export([list_max/1]).
          │ │ │ │  
          │ │ │ │ -list_max([Head|Rest]) ->
          │ │ │ │ -   list_max(Rest, Head).
          │ │ │ │ +list_max([Head|Rest]) ->
          │ │ │ │ +   list_max(Rest, Head).
          │ │ │ │  
          │ │ │ │ -list_max([], Res) ->
          │ │ │ │ +list_max([], Res) ->
          │ │ │ │      Res;
          │ │ │ │ -list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
          │ │ │ │ -    list_max(Rest, Head);
          │ │ │ │ -list_max([Head|Rest], Result_so_far)  ->
          │ │ │ │ -    list_max(Rest, Result_so_far).
          37> c(tut6).
          │ │ │ │ -{ok,tut6}
          │ │ │ │ -38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
          │ │ │ │ +list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
          │ │ │ │ +    list_max(Rest, Head);
          │ │ │ │ +list_max([Head|Rest], Result_so_far)  ->
          │ │ │ │ +    list_max(Rest, Result_so_far).
          37> c(tut6).
          │ │ │ │ +{ok,tut6}
          │ │ │ │ +38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
          │ │ │ │  7

          First notice that two functions have the same name, list_max. However, each of │ │ │ │ these takes a different number of arguments (parameters). In Erlang these are │ │ │ │ regarded as completely different functions. Where you need to distinguish │ │ │ │ between these functions, you write Name/Arity, where Name is the function name │ │ │ │ and Arity is the number of arguments, in this case list_max/1 and │ │ │ │ list_max/2.

          In this example you walk through a list "carrying" a value, in this case │ │ │ │ Result_so_far. list_max/1 simply assumes that the max value of the list is │ │ │ │ @@ -483,180 +483,180 @@ │ │ │ │ 5 │ │ │ │ 40> M = 6. │ │ │ │ ** exception error: no match of right hand side value 6 │ │ │ │ 41> M = M + 1. │ │ │ │ ** exception error: no match of right hand side value 6 │ │ │ │ 42> N = M + 1. │ │ │ │ 6

          The use of the match operator is particularly useful for pulling apart Erlang │ │ │ │ -terms and creating new ones.

          43> {X, Y} = {paris, {f, 28}}.
          │ │ │ │ -{paris,{f,28}}
          │ │ │ │ +terms and creating new ones.

          43> {X, Y} = {paris, {f, 28}}.
          │ │ │ │ +{paris,{f,28}}
          │ │ │ │  44> X.
          │ │ │ │  paris
          │ │ │ │  45> Y.
          │ │ │ │ -{f,28}

          Here X gets the value paris and Y the value {f,28}.

          If you try to do the same again with another city, an error is returned:

          46> {X, Y} = {london, {f, 36}}.
          │ │ │ │ +{f,28}

          Here X gets the value paris and Y the value {f,28}.

          If you try to do the same again with another city, an error is returned:

          46> {X, Y} = {london, {f, 36}}.
          │ │ │ │  ** exception error: no match of right hand side value {london,{f,36}}

          Variables can also be used to improve the readability of programs. For example, │ │ │ │ -in function list_max/2 above, you can write:

          list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
          │ │ │ │ +in function list_max/2 above, you can write:

          list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
          │ │ │ │      New_result_far = Head,
          │ │ │ │ -    list_max(Rest, New_result_far);

          This is possibly a little clearer.

          │ │ │ │ + list_max(Rest, New_result_far);

          This is possibly a little clearer.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ More About Lists │ │ │ │

          │ │ │ │ -

          Remember that the | operator can be used to get the head of a list:

          47> [M1|T1] = [paris, london, rome].
          │ │ │ │ -[paris,london,rome]
          │ │ │ │ +

          Remember that the | operator can be used to get the head of a list:

          47> [M1|T1] = [paris, london, rome].
          │ │ │ │ +[paris,london,rome]
          │ │ │ │  48> M1.
          │ │ │ │  paris
          │ │ │ │  49> T1.
          │ │ │ │ -[london,rome]

          The | operator can also be used to add a head to a list:

          50> L1 = [madrid | T1].
          │ │ │ │ -[madrid,london,rome]
          │ │ │ │ +[london,rome]

          The | operator can also be used to add a head to a list:

          50> L1 = [madrid | T1].
          │ │ │ │ +[madrid,london,rome]
          │ │ │ │  51> L1.
          │ │ │ │ -[madrid,london,rome]

          Now an example of this when working with lists - reversing the order of a list:

          -module(tut8).
          │ │ │ │ +[madrid,london,rome]

          Now an example of this when working with lists - reversing the order of a list:

          -module(tut8).
          │ │ │ │  
          │ │ │ │ --export([reverse/1]).
          │ │ │ │ +-export([reverse/1]).
          │ │ │ │  
          │ │ │ │ -reverse(List) ->
          │ │ │ │ -    reverse(List, []).
          │ │ │ │ +reverse(List) ->
          │ │ │ │ +    reverse(List, []).
          │ │ │ │  
          │ │ │ │ -reverse([Head | Rest], Reversed_List) ->
          │ │ │ │ -    reverse(Rest, [Head | Reversed_List]);
          │ │ │ │ -reverse([], Reversed_List) ->
          │ │ │ │ -    Reversed_List.
          52> c(tut8).
          │ │ │ │ -{ok,tut8}
          │ │ │ │ -53> tut8:reverse([1,2,3]).
          │ │ │ │ -[3,2,1]

          Consider how Reversed_List is built. It starts as [], then successively the │ │ │ │ +reverse([Head | Rest], Reversed_List) -> │ │ │ │ + reverse(Rest, [Head | Reversed_List]); │ │ │ │ +reverse([], Reversed_List) -> │ │ │ │ + Reversed_List.

          52> c(tut8).
          │ │ │ │ +{ok,tut8}
          │ │ │ │ +53> tut8:reverse([1,2,3]).
          │ │ │ │ +[3,2,1]

          Consider how Reversed_List is built. It starts as [], then successively the │ │ │ │ heads are taken off of the list to be reversed and added to the the │ │ │ │ -Reversed_List, as shown in the following:

          reverse([1|2,3], []) =>
          │ │ │ │ -    reverse([2,3], [1|[]])
          │ │ │ │ +Reversed_List, as shown in the following:

          reverse([1|2,3], []) =>
          │ │ │ │ +    reverse([2,3], [1|[]])
          │ │ │ │  
          │ │ │ │ -reverse([2|3], [1]) =>
          │ │ │ │ -    reverse([3], [2|[1])
          │ │ │ │ +reverse([2|3], [1]) =>
          │ │ │ │ +    reverse([3], [2|[1])
          │ │ │ │  
          │ │ │ │ -reverse([3|[]], [2,1]) =>
          │ │ │ │ -    reverse([], [3|[2,1]])
          │ │ │ │ +reverse([3|[]], [2,1]) =>
          │ │ │ │ +    reverse([], [3|[2,1]])
          │ │ │ │  
          │ │ │ │ -reverse([], [3,2,1]) =>
          │ │ │ │ -    [3,2,1]

          The module lists contains many functions for manipulating lists, for example, │ │ │ │ +reverse([], [3,2,1]) => │ │ │ │ + [3,2,1]

          The module lists contains many functions for manipulating lists, for example, │ │ │ │ for reversing them. So before writing a list-manipulating function it is a good │ │ │ │ idea to check if one not already is written for you (see the lists manual │ │ │ │ page in STDLIB).

          Now let us get back to the cities and temperatures, but take a more structured │ │ │ │ -approach this time. First let us convert the whole list to Celsius as follows:

          -module(tut7).
          │ │ │ │ --export([format_temps/1]).
          │ │ │ │ +approach this time. First let us convert the whole list to Celsius as follows:

          -module(tut7).
          │ │ │ │ +-export([format_temps/1]).
          │ │ │ │  
          │ │ │ │ -format_temps(List_of_cities) ->
          │ │ │ │ -    convert_list_to_c(List_of_cities).
          │ │ │ │ +format_temps(List_of_cities) ->
          │ │ │ │ +    convert_list_to_c(List_of_cities).
          │ │ │ │  
          │ │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
          │ │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
          │ │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([City | Rest]) ->
          │ │ │ │ -    [City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([]) ->
          │ │ │ │ -    [].

          Test the function:

          54> c(tut7).
          │ │ │ │ -{ok, tut7}.
          │ │ │ │ -55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ -[{moscow,{c,-10}},
          │ │ │ │ - {cape_town,{c,21.11111111111111}},
          │ │ │ │ - {stockholm,{c,-4}},
          │ │ │ │ - {paris,{c,-2.2222222222222223}},
          │ │ │ │ - {london,{c,2.2222222222222223}}]

          Explanation:

          format_temps(List_of_cities) ->
          │ │ │ │ -    convert_list_to_c(List_of_cities).

          Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) -> │ │ │ │ + Converted_City = {Name, {c, (F -32)* 5 / 9}}, │ │ │ │ + [Converted_City | convert_list_to_c(Rest)]; │ │ │ │ + │ │ │ │ +convert_list_to_c([City | Rest]) -> │ │ │ │ + [City | convert_list_to_c(Rest)]; │ │ │ │ + │ │ │ │ +convert_list_to_c([]) -> │ │ │ │ + [].

          Test the function:

          54> c(tut7).
          │ │ │ │ +{ok, tut7}.
          │ │ │ │ +55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +[{moscow,{c,-10}},
          │ │ │ │ + {cape_town,{c,21.11111111111111}},
          │ │ │ │ + {stockholm,{c,-4}},
          │ │ │ │ + {paris,{c,-2.2222222222222223}},
          │ │ │ │ + {london,{c,2.2222222222222223}}]

          Explanation:

          format_temps(List_of_cities) ->
          │ │ │ │ +    convert_list_to_c(List_of_cities).

          Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ │ off the head of the List_of_cities, converts it to Celsius if needed. The | │ │ │ │ -operator is used to add the (maybe) converted to the converted rest of the list:

          [Converted_City | convert_list_to_c(Rest)];

          or:

          [City | convert_list_to_c(Rest)];

          This is done until the end of the list is reached, that is, the list is empty:

          convert_list_to_c([]) ->
          │ │ │ │ -    [].

          Now when the list is converted, a function to print it is added:

          -module(tut7).
          │ │ │ │ --export([format_temps/1]).
          │ │ │ │ -
          │ │ │ │ -format_temps(List_of_cities) ->
          │ │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
          │ │ │ │ -    print_temp(Converted_List).
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
          │ │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
          │ │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([City | Rest]) ->
          │ │ │ │ -    [City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([]) ->
          │ │ │ │ -    [].
          │ │ │ │ -
          │ │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
          │ │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
          │ │ │ │ -    print_temp(Rest);
          │ │ │ │ -print_temp([]) ->
          │ │ │ │ -    ok.
          56> c(tut7).
          │ │ │ │ -{ok,tut7}
          │ │ │ │ -57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +operator is used to add the (maybe) converted to the converted rest of the list:

          [Converted_City | convert_list_to_c(Rest)];

          or:

          [City | convert_list_to_c(Rest)];

          This is done until the end of the list is reached, that is, the list is empty:

          convert_list_to_c([]) ->
          │ │ │ │ +    [].

          Now when the list is converted, a function to print it is added:

          -module(tut7).
          │ │ │ │ +-export([format_temps/1]).
          │ │ │ │ +
          │ │ │ │ +format_temps(List_of_cities) ->
          │ │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
          │ │ │ │ +    print_temp(Converted_List).
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) ->
          │ │ │ │ +    Converted_City = {Name, {c, (F -32)* 5 / 9}},
          │ │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([City | Rest]) ->
          │ │ │ │ +    [City | convert_list_to_c(Rest)];
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([]) ->
          │ │ │ │ +    [].
          │ │ │ │ +
          │ │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
          │ │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
          │ │ │ │ +    print_temp(Rest);
          │ │ │ │ +print_temp([]) ->
          │ │ │ │ +    ok.
          56> c(tut7).
          │ │ │ │ +{ok,tut7}
          │ │ │ │ +57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │  moscow          -10 c
          │ │ │ │  cape_town       21.11111111111111 c
          │ │ │ │  stockholm       -4 c
          │ │ │ │  paris           -2.2222222222222223 c
          │ │ │ │  london          2.2222222222222223 c
          │ │ │ │  ok

          Now a function has to be added to find the cities with the maximum and minimum │ │ │ │ temperatures. The following program is not the most efficient way of doing this │ │ │ │ as you walk through the list of cities four times. But it is better to first │ │ │ │ strive for clarity and correctness and to make programs efficient only if │ │ │ │ -needed.

          -module(tut7).
          │ │ │ │ --export([format_temps/1]).
          │ │ │ │ +needed.

          -module(tut7).
          │ │ │ │ +-export([format_temps/1]).
          │ │ │ │  
          │ │ │ │ -format_temps(List_of_cities) ->
          │ │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
          │ │ │ │ -    print_temp(Converted_List),
          │ │ │ │ -    {Max_city, Min_city} = find_max_and_min(Converted_List),
          │ │ │ │ -    print_max_and_min(Max_city, Min_city).
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
          │ │ │ │ -    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
          │ │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([City | Rest]) ->
          │ │ │ │ -    [City | convert_list_to_c(Rest)];
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c([]) ->
          │ │ │ │ -    [].
          │ │ │ │ -
          │ │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
          │ │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
          │ │ │ │ -    print_temp(Rest);
          │ │ │ │ -print_temp([]) ->
          │ │ │ │ +format_temps(List_of_cities) ->
          │ │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
          │ │ │ │ +    print_temp(Converted_List),
          │ │ │ │ +    {Max_city, Min_city} = find_max_and_min(Converted_List),
          │ │ │ │ +    print_max_and_min(Max_city, Min_city).
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
          │ │ │ │ +    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
          │ │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([City | Rest]) ->
          │ │ │ │ +    [City | convert_list_to_c(Rest)];
          │ │ │ │ +
          │ │ │ │ +convert_list_to_c([]) ->
          │ │ │ │ +    [].
          │ │ │ │ +
          │ │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
          │ │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
          │ │ │ │ +    print_temp(Rest);
          │ │ │ │ +print_temp([]) ->
          │ │ │ │      ok.
          │ │ │ │  
          │ │ │ │ -find_max_and_min([City | Rest]) ->
          │ │ │ │ -    find_max_and_min(Rest, City, City).
          │ │ │ │ +find_max_and_min([City | Rest]) ->
          │ │ │ │ +    find_max_and_min(Rest, City, City).
          │ │ │ │  
          │ │ │ │ -find_max_and_min([{Name, {c, Temp}} | Rest],
          │ │ │ │ -         {Max_Name, {c, Max_Temp}},
          │ │ │ │ -         {Min_Name, {c, Min_Temp}}) ->
          │ │ │ │ +find_max_and_min([{Name, {c, Temp}} | Rest],
          │ │ │ │ +         {Max_Name, {c, Max_Temp}},
          │ │ │ │ +         {Min_Name, {c, Min_Temp}}) ->
          │ │ │ │      if
          │ │ │ │          Temp > Max_Temp ->
          │ │ │ │ -            Max_City = {Name, {c, Temp}};           % Change
          │ │ │ │ +            Max_City = {Name, {c, Temp}};           % Change
          │ │ │ │          true ->
          │ │ │ │ -            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
          │ │ │ │ +            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
          │ │ │ │      end,
          │ │ │ │      if
          │ │ │ │           Temp < Min_Temp ->
          │ │ │ │ -            Min_City = {Name, {c, Temp}};           % Change
          │ │ │ │ +            Min_City = {Name, {c, Temp}};           % Change
          │ │ │ │          true ->
          │ │ │ │ -            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
          │ │ │ │ +            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
          │ │ │ │      end,
          │ │ │ │ -    find_max_and_min(Rest, Max_City, Min_City);
          │ │ │ │ +    find_max_and_min(Rest, Max_City, Min_City);
          │ │ │ │  
          │ │ │ │ -find_max_and_min([], Max_City, Min_City) ->
          │ │ │ │ -    {Max_City, Min_City}.
          │ │ │ │ +find_max_and_min([], Max_City, Min_City) ->
          │ │ │ │ +    {Max_City, Min_City}.
          │ │ │ │  
          │ │ │ │ -print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
          │ │ │ │ -    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
          │ │ │ │ -    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
          58> c(tut7).
          │ │ │ │ -{ok, tut7}
          │ │ │ │ -59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
          │ │ │ │ +    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
          │ │ │ │ +    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
          58> c(tut7).
          │ │ │ │ +{ok, tut7}
          │ │ │ │ +59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │  moscow          -10 c
          │ │ │ │  cape_town       21.11111111111111 c
          │ │ │ │  stockholm       -4 c
          │ │ │ │  paris           -2.2222222222222223 c
          │ │ │ │  london          2.2222222222222223 c
          │ │ │ │  Max temperature was 21.11111111111111 c in cape_town
          │ │ │ │  Min temperature was -10 c in moscow
          │ │ │ │ @@ -678,88 +678,88 @@
          │ │ │ │          Action 4
          │ │ │ │  end

          Notice that there is no ; before end. Conditions do the same as guards, that │ │ │ │ is, tests that succeed or fail. Erlang starts at the top and tests until it │ │ │ │ finds a condition that succeeds. Then it evaluates (performs) the action │ │ │ │ following the condition and ignores all other conditions and actions before the │ │ │ │ end. If no condition matches, a run-time failure occurs. A condition that │ │ │ │ always succeeds is the atom true. This is often used last in an if, meaning, │ │ │ │ -do the action following the true if all other conditions have failed.

          The following is a short program to show the workings of if.

          -module(tut9).
          │ │ │ │ --export([test_if/2]).
          │ │ │ │ +do the action following the true if all other conditions have failed.

          The following is a short program to show the workings of if.

          -module(tut9).
          │ │ │ │ +-export([test_if/2]).
          │ │ │ │  
          │ │ │ │ -test_if(A, B) ->
          │ │ │ │ +test_if(A, B) ->
          │ │ │ │      if
          │ │ │ │          A == 5 ->
          │ │ │ │ -            io:format("A == 5~n", []),
          │ │ │ │ +            io:format("A == 5~n", []),
          │ │ │ │              a_equals_5;
          │ │ │ │          B == 6 ->
          │ │ │ │ -            io:format("B == 6~n", []),
          │ │ │ │ +            io:format("B == 6~n", []),
          │ │ │ │              b_equals_6;
          │ │ │ │          A == 2, B == 3 ->                      %That is A equals 2 and B equals 3
          │ │ │ │ -            io:format("A == 2, B == 3~n", []),
          │ │ │ │ +            io:format("A == 2, B == 3~n", []),
          │ │ │ │              a_equals_2_b_equals_3;
          │ │ │ │          A == 1 ; B == 7 ->                     %That is A equals 1 or B equals 7
          │ │ │ │ -            io:format("A == 1 ; B == 7~n", []),
          │ │ │ │ +            io:format("A == 1 ; B == 7~n", []),
          │ │ │ │              a_equals_1_or_b_equals_7
          │ │ │ │ -    end.

          Testing this program gives:

          60> c(tut9).
          │ │ │ │ -{ok,tut9}
          │ │ │ │ -61> tut9:test_if(5,33).
          │ │ │ │ +    end.

          Testing this program gives:

          60> c(tut9).
          │ │ │ │ +{ok,tut9}
          │ │ │ │ +61> tut9:test_if(5,33).
          │ │ │ │  A == 5
          │ │ │ │  a_equals_5
          │ │ │ │ -62> tut9:test_if(33,6).
          │ │ │ │ +62> tut9:test_if(33,6).
          │ │ │ │  B == 6
          │ │ │ │  b_equals_6
          │ │ │ │ -63> tut9:test_if(2, 3).
          │ │ │ │ +63> tut9:test_if(2, 3).
          │ │ │ │  A == 2, B == 3
          │ │ │ │  a_equals_2_b_equals_3
          │ │ │ │ -64> tut9:test_if(1, 33).
          │ │ │ │ +64> tut9:test_if(1, 33).
          │ │ │ │  A == 1 ; B == 7
          │ │ │ │  a_equals_1_or_b_equals_7
          │ │ │ │ -65> tut9:test_if(33, 7).
          │ │ │ │ +65> tut9:test_if(33, 7).
          │ │ │ │  A == 1 ; B == 7
          │ │ │ │  a_equals_1_or_b_equals_7
          │ │ │ │ -66> tut9:test_if(33, 33).
          │ │ │ │ +66> tut9:test_if(33, 33).
          │ │ │ │  ** exception error: no true branch found when evaluating an if expression
          │ │ │ │       in function  tut9:test_if/2 (tut9.erl, line 5)

          Notice that tut9:test_if(33,33) does not cause any condition to succeed. This │ │ │ │ leads to the run time error if_clause, here nicely formatted by the shell. See │ │ │ │ Guard Sequences for details of the many guard tests │ │ │ │ available.

          case is another construct in Erlang. Recall that the convert_length function │ │ │ │ -was written as:

          convert_length({centimeter, X}) ->
          │ │ │ │ -    {inch, X / 2.54};
          │ │ │ │ -convert_length({inch, Y}) ->
          │ │ │ │ -    {centimeter, Y * 2.54}.

          The same program can also be written as:

          -module(tut10).
          │ │ │ │ --export([convert_length/1]).
          │ │ │ │ +was written as:

          convert_length({centimeter, X}) ->
          │ │ │ │ +    {inch, X / 2.54};
          │ │ │ │ +convert_length({inch, Y}) ->
          │ │ │ │ +    {centimeter, Y * 2.54}.

          The same program can also be written as:

          -module(tut10).
          │ │ │ │ +-export([convert_length/1]).
          │ │ │ │  
          │ │ │ │ -convert_length(Length) ->
          │ │ │ │ +convert_length(Length) ->
          │ │ │ │      case Length of
          │ │ │ │ -        {centimeter, X} ->
          │ │ │ │ -            {inch, X / 2.54};
          │ │ │ │ -        {inch, Y} ->
          │ │ │ │ -            {centimeter, Y * 2.54}
          │ │ │ │ -    end.
          67> c(tut10).
          │ │ │ │ -{ok,tut10}
          │ │ │ │ -68> tut10:convert_length({inch, 6}).
          │ │ │ │ -{centimeter,15.24}
          │ │ │ │ -69> tut10:convert_length({centimeter, 2.5}).
          │ │ │ │ -{inch,0.984251968503937}

          Both case and if have return values, that is, in the above example case │ │ │ │ + {centimeter, X} -> │ │ │ │ + {inch, X / 2.54}; │ │ │ │ + {inch, Y} -> │ │ │ │ + {centimeter, Y * 2.54} │ │ │ │ + end.

          67> c(tut10).
          │ │ │ │ +{ok,tut10}
          │ │ │ │ +68> tut10:convert_length({inch, 6}).
          │ │ │ │ +{centimeter,15.24}
          │ │ │ │ +69> tut10:convert_length({centimeter, 2.5}).
          │ │ │ │ +{inch,0.984251968503937}

          Both case and if have return values, that is, in the above example case │ │ │ │ returned either {inch,X/2.54} or {centimeter,Y*2.54}. The behaviour of │ │ │ │ case can also be modified by using guards. The following example clarifies │ │ │ │ this. It tells us the length of a month, given the year. The year must be known, │ │ │ │ -since February has 29 days in a leap year.

          -module(tut11).
          │ │ │ │ --export([month_length/2]).
          │ │ │ │ +since February has 29 days in a leap year.

          -module(tut11).
          │ │ │ │ +-export([month_length/2]).
          │ │ │ │  
          │ │ │ │ -month_length(Year, Month) ->
          │ │ │ │ +month_length(Year, Month) ->
          │ │ │ │      %% All years divisible by 400 are leap
          │ │ │ │      %% Years divisible by 100 are not leap (except the 400 rule above)
          │ │ │ │      %% Years divisible by 4 are leap (except the 100 rule above)
          │ │ │ │      Leap = if
          │ │ │ │ -        trunc(Year / 400) * 400 == Year ->
          │ │ │ │ +        trunc(Year / 400) * 400 == Year ->
          │ │ │ │              leap;
          │ │ │ │ -        trunc(Year / 100) * 100 == Year ->
          │ │ │ │ +        trunc(Year / 100) * 100 == Year ->
          │ │ │ │              not_leap;
          │ │ │ │ -        trunc(Year / 4) * 4 == Year ->
          │ │ │ │ +        trunc(Year / 4) * 4 == Year ->
          │ │ │ │              leap;
          │ │ │ │          true ->
          │ │ │ │              not_leap
          │ │ │ │      end,
          │ │ │ │      case Month of
          │ │ │ │          sep -> 30;
          │ │ │ │          apr -> 30;
          │ │ │ │ @@ -770,151 +770,151 @@
          │ │ │ │          jan -> 31;
          │ │ │ │          mar -> 31;
          │ │ │ │          may -> 31;
          │ │ │ │          jul -> 31;
          │ │ │ │          aug -> 31;
          │ │ │ │          oct -> 31;
          │ │ │ │          dec -> 31
          │ │ │ │ -    end.
          70> c(tut11).
          │ │ │ │ -{ok,tut11}
          │ │ │ │ -71> tut11:month_length(2004, feb).
          │ │ │ │ +    end.
          70> c(tut11).
          │ │ │ │ +{ok,tut11}
          │ │ │ │ +71> tut11:month_length(2004, feb).
          │ │ │ │  29
          │ │ │ │ -72> tut11:month_length(2003, feb).
          │ │ │ │ +72> tut11:month_length(2003, feb).
          │ │ │ │  28
          │ │ │ │ -73> tut11:month_length(1947, aug).
          │ │ │ │ +73> tut11:month_length(1947, aug).
          │ │ │ │  31

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │ │

          │ │ │ │

          BIFs are functions that for some reason are built-in to the Erlang virtual │ │ │ │ machine. BIFs often implement functionality that is impossible or is too │ │ │ │ inefficient to implement in Erlang. Some BIFs can be called using the function │ │ │ │ name only but they are by default belonging to the erlang module. For example, │ │ │ │ the call to the BIF trunc below is equivalent to a call to erlang:trunc.

          As shown, first it is checked if a year is leap. If a year is divisible by 400, │ │ │ │ it is a leap year. To determine this, first divide the year by 400 and use the │ │ │ │ BIF trunc (more about this later) to cut off any decimals. Then multiply by │ │ │ │ 400 again and see if the same value is returned again. For example, year 2004:

          2004 / 400 = 5.01
          │ │ │ │ -trunc(5.01) = 5
          │ │ │ │ +trunc(5.01) = 5
          │ │ │ │  5 * 400 = 2000

          2000 is not the same as 2004, so 2004 is not divisible by 400. Year 2000:

          2000 / 400 = 5.0
          │ │ │ │ -trunc(5.0) = 5
          │ │ │ │ +trunc(5.0) = 5
          │ │ │ │  5 * 400 = 2000

          That is, a leap year. The next two trunc-tests evaluate if the year is │ │ │ │ divisible by 100 or 4 in the same way. The first if returns leap or │ │ │ │ not_leap, which lands up in the variable Leap. This variable is used in the │ │ │ │ guard for feb in the following case that tells us how long the month is.

          This example showed the use of trunc. It is easier to use the Erlang operator │ │ │ │ rem that gives the remainder after division, for example:

          74> 2004 rem 400.
          │ │ │ │ -4

          So instead of writing:

          trunc(Year / 400) * 400 == Year ->
          │ │ │ │ +4

          So instead of writing:

          trunc(Year / 400) * 400 == Year ->
          │ │ │ │      leap;

          it can be written:

          Year rem 400 == 0 ->
          │ │ │ │      leap;

          There are many other BIFs such as trunc. Only a few BIFs can be used in │ │ │ │ guards, and you cannot use functions you have defined yourself in guards. (see │ │ │ │ Guard Sequences) (For advanced readers: This is to │ │ │ │ ensure that guards do not have side effects.) Let us play with a few of these │ │ │ │ -functions in the shell:

          75> trunc(5.6).
          │ │ │ │ +functions in the shell:

          75> trunc(5.6).
          │ │ │ │  5
          │ │ │ │ -76> round(5.6).
          │ │ │ │ +76> round(5.6).
          │ │ │ │  6
          │ │ │ │ -77> length([a,b,c,d]).
          │ │ │ │ +77> length([a,b,c,d]).
          │ │ │ │  4
          │ │ │ │ -78> float(5).
          │ │ │ │ +78> float(5).
          │ │ │ │  5.0
          │ │ │ │ -79> is_atom(hello).
          │ │ │ │ +79> is_atom(hello).
          │ │ │ │  true
          │ │ │ │ -80> is_atom("hello").
          │ │ │ │ +80> is_atom("hello").
          │ │ │ │  false
          │ │ │ │ -81> is_tuple({paris, {c, 30}}).
          │ │ │ │ +81> is_tuple({paris, {c, 30}}).
          │ │ │ │  true
          │ │ │ │ -82> is_tuple([paris, {c, 30}]).
          │ │ │ │ +82> is_tuple([paris, {c, 30}]).
          │ │ │ │  false

          All of these can be used in guards. Now for some BIFs that cannot be used in │ │ │ │ -guards:

          83> atom_to_list(hello).
          │ │ │ │ +guards:

          83> atom_to_list(hello).
          │ │ │ │  "hello"
          │ │ │ │ -84> list_to_atom("goodbye").
          │ │ │ │ +84> list_to_atom("goodbye").
          │ │ │ │  goodbye
          │ │ │ │ -85> integer_to_list(22).
          │ │ │ │ +85> integer_to_list(22).
          │ │ │ │  "22"

          These three BIFs do conversions that would be difficult (or impossible) to do in │ │ │ │ Erlang.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Higher-Order Functions (Funs) │ │ │ │

          │ │ │ │

          Erlang, like most modern functional programming languages, has higher-order │ │ │ │ -functions. Here is an example using the shell:

          86> Xf = fun(X) -> X * 2 end.
          │ │ │ │ +functions. Here is an example using the shell:

          86> Xf = fun(X) -> X * 2 end.
          │ │ │ │  #Fun<erl_eval.5.123085357>
          │ │ │ │ -87> Xf(5).
          │ │ │ │ +87> Xf(5).
          │ │ │ │  10

          Here is defined a function that doubles the value of a number and assigned this │ │ │ │ function to a variable. Thus Xf(5) returns value 10. Two useful functions when │ │ │ │ -working with lists are foreach and map, which are defined as follows:

          foreach(Fun, [First|Rest]) ->
          │ │ │ │ -    Fun(First),
          │ │ │ │ -    foreach(Fun, Rest);
          │ │ │ │ -foreach(Fun, []) ->
          │ │ │ │ +working with lists are foreach and map, which are defined as follows:

          foreach(Fun, [First|Rest]) ->
          │ │ │ │ +    Fun(First),
          │ │ │ │ +    foreach(Fun, Rest);
          │ │ │ │ +foreach(Fun, []) ->
          │ │ │ │      ok.
          │ │ │ │  
          │ │ │ │ -map(Fun, [First|Rest]) ->
          │ │ │ │ -    [Fun(First)|map(Fun,Rest)];
          │ │ │ │ -map(Fun, []) ->
          │ │ │ │ -    [].

          These two functions are provided in the standard module lists. foreach takes │ │ │ │ +map(Fun, [First|Rest]) -> │ │ │ │ + [Fun(First)|map(Fun,Rest)]; │ │ │ │ +map(Fun, []) -> │ │ │ │ + [].

          These two functions are provided in the standard module lists. foreach takes │ │ │ │ a list and applies a fun to every element in the list. map creates a new list │ │ │ │ by applying a fun to every element in a list. Going back to the shell, map is │ │ │ │ -used and a fun to add 3 to every element of a list:

          88> Add_3 = fun(X) -> X + 3 end.
          │ │ │ │ +used and a fun to add 3 to every element of a list:

          88> Add_3 = fun(X) -> X + 3 end.
          │ │ │ │  #Fun<erl_eval.5.123085357>
          │ │ │ │ -89> lists:map(Add_3, [1,2,3]).
          │ │ │ │ -[4,5,6]

          Let us (again) print the temperatures in a list of cities:

          90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
          │ │ │ │ -[City, X, Temp]) end.
          │ │ │ │ +89> lists:map(Add_3, [1,2,3]).
          │ │ │ │ +[4,5,6]

          Let us (again) print the temperatures in a list of cities:

          90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
          │ │ │ │ +[City, X, Temp]) end.
          │ │ │ │  #Fun<erl_eval.5.123085357>
          │ │ │ │ -91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │  moscow          c -10
          │ │ │ │  cape_town       f 70
          │ │ │ │  stockholm       c -4
          │ │ │ │  paris           f 28
          │ │ │ │  london          f 36
          │ │ │ │  ok

          Let us now define a fun that can be used to go through a list of cities and │ │ │ │ -temperatures and transform them all to Celsius.

          -module(tut13).
          │ │ │ │ +temperatures and transform them all to Celsius.

          -module(tut13).
          │ │ │ │  
          │ │ │ │ --export([convert_list_to_c/1]).
          │ │ │ │ +-export([convert_list_to_c/1]).
          │ │ │ │  
          │ │ │ │ -convert_to_c({Name, {f, Temp}}) ->
          │ │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
          │ │ │ │ -convert_to_c({Name, {c, Temp}}) ->
          │ │ │ │ -    {Name, {c, Temp}}.
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c(List) ->
          │ │ │ │ -    lists:map(fun convert_to_c/1, List).
          92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ -[{moscow,{c,-10}},
          │ │ │ │ - {cape_town,{c,21}},
          │ │ │ │ - {stockholm,{c,-4}},
          │ │ │ │ - {paris,{c,-2}},
          │ │ │ │ - {london,{c,2}}]

          The convert_to_c function is the same as before, but here it is used as a fun:

          lists:map(fun convert_to_c/1, List)

          When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ │ + {Name, {c, Temp}}. │ │ │ │ + │ │ │ │ +convert_list_to_c(List) -> │ │ │ │ + lists:map(fun convert_to_c/1, List).

          92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +[{moscow,{c,-10}},
          │ │ │ │ + {cape_town,{c,21}},
          │ │ │ │ + {stockholm,{c,-4}},
          │ │ │ │ + {paris,{c,-2}},
          │ │ │ │ + {london,{c,2}}]

          The convert_to_c function is the same as before, but here it is used as a fun:

          lists:map(fun convert_to_c/1, List)

          When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ │ Function/Arity (remember that Arity = number of arguments). So in the │ │ │ │ map-call lists:map(fun convert_to_c/1, List) is written. As shown, │ │ │ │ convert_list_to_c becomes much shorter and easier to understand.

          The standard module lists also contains a function sort(Fun, List) where │ │ │ │ Fun is a fun with two arguments. This fun returns true if the first argument │ │ │ │ is less than the second argument, or else false. Sorting is added to the │ │ │ │ -convert_list_to_c:

          -module(tut13).
          │ │ │ │ +convert_list_to_c:

          -module(tut13).
          │ │ │ │  
          │ │ │ │ --export([convert_list_to_c/1]).
          │ │ │ │ +-export([convert_list_to_c/1]).
          │ │ │ │  
          │ │ │ │ -convert_to_c({Name, {f, Temp}}) ->
          │ │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
          │ │ │ │ -convert_to_c({Name, {c, Temp}}) ->
          │ │ │ │ -    {Name, {c, Temp}}.
          │ │ │ │ -
          │ │ │ │ -convert_list_to_c(List) ->
          │ │ │ │ -    New_list = lists:map(fun convert_to_c/1, List),
          │ │ │ │ -    lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->
          │ │ │ │ -                       Temp1 < Temp2 end, New_list).
          93> c(tut13).
          │ │ │ │ -{ok,tut13}
          │ │ │ │ -94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ -[{moscow,{c,-10}},
          │ │ │ │ - {stockholm,{c,-4}},
          │ │ │ │ - {paris,{c,-2}},
          │ │ │ │ - {london,{c,2}},
          │ │ │ │ - {cape_town,{c,21}}]

          In sort the fun is used:

          fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

          Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ │ + {Name, {c, Temp}}. │ │ │ │ + │ │ │ │ +convert_list_to_c(List) -> │ │ │ │ + New_list = lists:map(fun convert_to_c/1, List), │ │ │ │ + lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> │ │ │ │ + Temp1 < Temp2 end, New_list).

          93> c(tut13).
          │ │ │ │ +{ok,tut13}
          │ │ │ │ +94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
          │ │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
          │ │ │ │ +[{moscow,{c,-10}},
          │ │ │ │ + {stockholm,{c,-4}},
          │ │ │ │ + {paris,{c,-2}},
          │ │ │ │ + {london,{c,2}},
          │ │ │ │ + {cape_town,{c,21}}]

          In sort the fun is used:

          fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

          Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ │ shorthand for a variable that gets a value, but the value is ignored. This can │ │ │ │ be used anywhere suitable, not just in funs. Temp1 < Temp2 returns true if │ │ │ │ Temp1 is less than Temp2.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/robustness.xhtml │ │ │ │ @@ -33,68 +33,68 @@ │ │ │ │ │ │ │ │

          Before improving the messenger program, let us look at some general principles, │ │ │ │ using the ping pong program as an example. Recall that when "ping" finishes, it │ │ │ │ tells "pong" that it has done so by sending the atom finished as a message to │ │ │ │ "pong" so that "pong" can also finish. Another way to let "pong" finish is to │ │ │ │ make "pong" exit if it does not receive a message from ping within a certain │ │ │ │ time. This can be done by adding a time-out to pong as shown in the │ │ │ │ -following example:

          -module(tut19).
          │ │ │ │ +following example:

          -module(tut19).
          │ │ │ │  
          │ │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(0, Pong_Node) ->
          │ │ │ │ -    io:format("ping finished~n", []);
          │ │ │ │ +ping(0, Pong_Node) ->
          │ │ │ │ +    io:format("ping finished~n", []);
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Node) ->
          │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ │ +ping(N, Pong_Node) ->
          │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ +pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("Pong timed out~n", [])
          │ │ │ │ +            io:format("Pong timed out~n", [])
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start_pong() ->
          │ │ │ │ -    register(pong, spawn(tut19, pong, [])).
          │ │ │ │ +start_pong() ->
          │ │ │ │ +    register(pong, spawn(tut19, pong, [])).
          │ │ │ │  
          │ │ │ │ -start_ping(Pong_Node) ->
          │ │ │ │ -    spawn(tut19, ping, [3, Pong_Node]).

          After this is compiled and the file tut19.beam is copied to the necessary │ │ │ │ +start_ping(Pong_Node) -> │ │ │ │ + spawn(tut19, ping, [3, Pong_Node]).

          After this is compiled and the file tut19.beam is copied to the necessary │ │ │ │ directories, the following is seen on (pong@kosken):

          (pong@kosken)1> tut19:start_pong().
          │ │ │ │  true
          │ │ │ │  Pong received ping
          │ │ │ │  Pong received ping
          │ │ │ │  Pong received ping
          │ │ │ │  Pong timed out

          And the following is seen on (ping@gollum):

          (ping@gollum)1> tut19:start_ping(pong@kosken).
          │ │ │ │  <0.36.0>
          │ │ │ │  Ping received pong
          │ │ │ │  Ping received pong
          │ │ │ │  Ping received pong
          │ │ │ │ -ping finished

          The time-out is set in:

          pong() ->
          │ │ │ │ +ping finished

          The time-out is set in:

          pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("Pong timed out~n", [])
          │ │ │ │ +            io:format("Pong timed out~n", [])
          │ │ │ │      end.

          The time-out (after 5000) is started when receive is entered. The time-out │ │ │ │ is canceled if {ping,Ping_PID} is received. If {ping,Ping_PID} is not │ │ │ │ received, the actions following the time-out are done after 5000 milliseconds. │ │ │ │ after must be last in the receive, that is, preceded by all other message │ │ │ │ reception specifications in the receive. It is also possible to call a │ │ │ │ -function that returned an integer for the time-out:

          after pong_timeout() ->

          In general, there are better ways than using time-outs to supervise parts of a │ │ │ │ +function that returned an integer for the time-out:

          after pong_timeout() ->

          In general, there are better ways than using time-outs to supervise parts of a │ │ │ │ distributed Erlang system. Time-outs are usually appropriate to supervise │ │ │ │ external events, for example, if you have expected a message from some external │ │ │ │ system within a specified time. For example, a time-out can be used to log a │ │ │ │ user out of the messenger system if they have not accessed it for, say, ten │ │ │ │ minutes.

          │ │ │ │ │ │ │ │ │ │ │ │ @@ -114,96 +114,96 @@ │ │ │ │ something called a signal to all the processes it has links to.

          The signal carries information about the pid it was sent from and the exit │ │ │ │ reason.

          The default behaviour of a process that receives a normal exit is to ignore the │ │ │ │ signal.

          The default behaviour in the two other cases (that is, abnormal exit) above is │ │ │ │ to:

          • Bypass all messages to the receiving process.
          • Kill the receiving process.
          • Propagate the same error signal to the links of the killed process.

          In this way you can connect all processes in a transaction together using links. │ │ │ │ If one of the processes exits abnormally, all the processes in the transaction │ │ │ │ are killed. As it is often wanted to create a process and link to it at the same │ │ │ │ time, there is a special BIF, spawn_link that does the │ │ │ │ -same as spawn, but also creates a link to the spawned process.

          Now an example of the ping pong example using links to terminate "pong":

          -module(tut20).
          │ │ │ │ +same as spawn, but also creates a link to the spawned process.

          Now an example of the ping pong example using links to terminate "pong":

          -module(tut20).
          │ │ │ │  
          │ │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Pid) ->
          │ │ │ │ -    link(Pong_Pid),
          │ │ │ │ -    ping1(N, Pong_Pid).
          │ │ │ │ +ping(N, Pong_Pid) ->
          │ │ │ │ +    link(Pong_Pid),
          │ │ │ │ +    ping1(N, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -ping1(0, _) ->
          │ │ │ │ -    exit(ping);
          │ │ │ │ +ping1(0, _) ->
          │ │ │ │ +    exit(ping);
          │ │ │ │  
          │ │ │ │ -ping1(N, Pong_Pid) ->
          │ │ │ │ -    Pong_Pid ! {ping, self()},
          │ │ │ │ +ping1(N, Pong_Pid) ->
          │ │ │ │ +    Pong_Pid ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping1(N - 1, Pong_Pid).
          │ │ │ │ +    ping1(N - 1, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ +pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start(Ping_Node) ->
          │ │ │ │ -    PongPID = spawn(tut20, pong, []),
          │ │ │ │ -    spawn(Ping_Node, tut20, ping, [3, PongPID]).
          (s1@bill)3> tut20:start(s2@kosken).
          │ │ │ │ +start(Ping_Node) ->
          │ │ │ │ +    PongPID = spawn(tut20, pong, []),
          │ │ │ │ +    spawn(Ping_Node, tut20, ping, [3, PongPID]).
          (s1@bill)3> tut20:start(s2@kosken).
          │ │ │ │  Pong received ping
          │ │ │ │  <3820.41.0>
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong

          This is a slight modification of the ping pong program where both processes are │ │ │ │ spawned from the same start/1 function, and the "ping" process can be spawned │ │ │ │ on a separate node. Notice the use of the link BIF. "Ping" calls │ │ │ │ exit(ping) when it finishes and this causes an exit signal to be │ │ │ │ sent to "pong", which also terminates.

          It is possible to modify the default behaviour of a process so that it does not │ │ │ │ get killed when it receives abnormal exit signals. Instead, all signals are │ │ │ │ turned into normal messages on the format {'EXIT',FromPID,Reason} and added to │ │ │ │ -the end of the receiving process' message queue. This behaviour is set by:

          process_flag(trap_exit, true)

          There are several other process flags, see erlang(3). │ │ │ │ +the end of the receiving process' message queue. This behaviour is set by:

          process_flag(trap_exit, true)

          There are several other process flags, see erlang(3). │ │ │ │ Changing the default behaviour of a process in this way is usually not done in │ │ │ │ standard user programs, but is left to the supervisory programs in OTP. However, │ │ │ │ -the ping pong program is modified to illustrate exit trapping.

          -module(tut21).
          │ │ │ │ +the ping pong program is modified to illustrate exit trapping.

          -module(tut21).
          │ │ │ │  
          │ │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Pid) ->
          │ │ │ │ -    link(Pong_Pid),
          │ │ │ │ -    ping1(N, Pong_Pid).
          │ │ │ │ +ping(N, Pong_Pid) ->
          │ │ │ │ +    link(Pong_Pid),
          │ │ │ │ +    ping1(N, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -ping1(0, _) ->
          │ │ │ │ -    exit(ping);
          │ │ │ │ +ping1(0, _) ->
          │ │ │ │ +    exit(ping);
          │ │ │ │  
          │ │ │ │ -ping1(N, Pong_Pid) ->
          │ │ │ │ -    Pong_Pid ! {ping, self()},
          │ │ │ │ +ping1(N, Pong_Pid) ->
          │ │ │ │ +    Pong_Pid ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping1(N - 1, Pong_Pid).
          │ │ │ │ +    ping1(N - 1, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ -    process_flag(trap_exit, true),
          │ │ │ │ -    pong1().
          │ │ │ │ +pong() ->
          │ │ │ │ +    process_flag(trap_exit, true),
          │ │ │ │ +    pong1().
          │ │ │ │  
          │ │ │ │ -pong1() ->
          │ │ │ │ +pong1() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong1();
          │ │ │ │ -        {'EXIT', From, Reason} ->
          │ │ │ │ -            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
          │ │ │ │ +            pong1();
          │ │ │ │ +        {'EXIT', From, Reason} ->
          │ │ │ │ +            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start(Ping_Node) ->
          │ │ │ │ -    PongPID = spawn(tut21, pong, []),
          │ │ │ │ -    spawn(Ping_Node, tut21, ping, [3, PongPID]).
          (s1@bill)1> tut21:start(s2@gollum).
          │ │ │ │ +start(Ping_Node) ->
          │ │ │ │ +    PongPID = spawn(tut21, pong, []),
          │ │ │ │ +    spawn(Ping_Node, tut21, ping, [3, PongPID]).
          (s1@bill)1> tut21:start(s2@gollum).
          │ │ │ │  <3820.39.0>
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │ @@ -256,135 +256,135 @@
          │ │ │ │  %%% Started: messenger:client(Server_Node, Name)
          │ │ │ │  %%% To client: logoff
          │ │ │ │  %%% To client: {message_to, ToName, Message}
          │ │ │ │  %%%
          │ │ │ │  %%% Configuration: change the server_node() function to return the
          │ │ │ │  %%% name of the node where the messenger server runs
          │ │ │ │  
          │ │ │ │ --module(messenger).
          │ │ │ │ --export([start_server/0, server/0,
          │ │ │ │ -         logon/1, logoff/0, message/2, client/2]).
          │ │ │ │ +-module(messenger).
          │ │ │ │ +-export([start_server/0, server/0,
          │ │ │ │ +         logon/1, logoff/0, message/2, client/2]).
          │ │ │ │  
          │ │ │ │  %%% Change the function below to return the name of the node where the
          │ │ │ │  %%% messenger server runs
          │ │ │ │ -server_node() ->
          │ │ │ │ +server_node() ->
          │ │ │ │      messenger@super.
          │ │ │ │  
          │ │ │ │  %%% This is the server process for the "messenger"
          │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
          │ │ │ │ -server() ->
          │ │ │ │ -    process_flag(trap_exit, true),
          │ │ │ │ -    server([]).
          │ │ │ │ +server() ->
          │ │ │ │ +    process_flag(trap_exit, true),
          │ │ │ │ +    server([]).
          │ │ │ │  
          │ │ │ │ -server(User_List) ->
          │ │ │ │ +server(User_List) ->
          │ │ │ │      receive
          │ │ │ │ -        {From, logon, Name} ->
          │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
          │ │ │ │ -            server(New_User_List);
          │ │ │ │ -        {'EXIT', From, _} ->
          │ │ │ │ -            New_User_List = server_logoff(From, User_List),
          │ │ │ │ -            server(New_User_List);
          │ │ │ │ -        {From, message_to, To, Message} ->
          │ │ │ │ -            server_transfer(From, To, Message, User_List),
          │ │ │ │ -            io:format("list is now: ~p~n", [User_List]),
          │ │ │ │ -            server(User_List)
          │ │ │ │ +        {From, logon, Name} ->
          │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
          │ │ │ │ +            server(New_User_List);
          │ │ │ │ +        {'EXIT', From, _} ->
          │ │ │ │ +            New_User_List = server_logoff(From, User_List),
          │ │ │ │ +            server(New_User_List);
          │ │ │ │ +        {From, message_to, To, Message} ->
          │ │ │ │ +            server_transfer(From, To, Message, User_List),
          │ │ │ │ +            io:format("list is now: ~p~n", [User_List]),
          │ │ │ │ +            server(User_List)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% Start the server
          │ │ │ │ -start_server() ->
          │ │ │ │ -    register(messenger, spawn(messenger, server, [])).
          │ │ │ │ +start_server() ->
          │ │ │ │ +    register(messenger, spawn(messenger, server, [])).
          │ │ │ │  
          │ │ │ │  %%% Server adds a new user to the user list
          │ │ │ │ -server_logon(From, Name, User_List) ->
          │ │ │ │ +server_logon(From, Name, User_List) ->
          │ │ │ │      %% check if logged on anywhere else
          │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
          │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
          │ │ │ │          true ->
          │ │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ │              User_List;
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, logged_on},
          │ │ │ │ -            link(From),
          │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
          │ │ │ │ +            From ! {messenger, logged_on},
          │ │ │ │ +            link(From),
          │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% Server deletes a user from the user list
          │ │ │ │ -server_logoff(From, User_List) ->
          │ │ │ │ -    lists:keydelete(From, 1, User_List).
          │ │ │ │ +server_logoff(From, User_List) ->
          │ │ │ │ +    lists:keydelete(From, 1, User_List).
          │ │ │ │  
          │ │ │ │  
          │ │ │ │  %%% Server transfers a message between user
          │ │ │ │ -server_transfer(From, To, Message, User_List) ->
          │ │ │ │ +server_transfer(From, To, Message, User_List) ->
          │ │ │ │      %% check that the user is logged on and who he is
          │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
          │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ -        {value, {_, Name}} ->
          │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ +        {value, {_, Name}} ->
          │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% If the user exists, send the message
          │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ │      %% Find the receiver and send the message
          │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
          │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ │ -        {value, {ToPid, To}} ->
          │ │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ │ -            From ! {messenger, sent}
          │ │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ │ +        {value, {ToPid, To}} ->
          │ │ │ │ +            ToPid ! {message_from, Name, Message},
          │ │ │ │ +            From ! {messenger, sent}
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% User Commands
          │ │ │ │ -logon(Name) ->
          │ │ │ │ -    case whereis(mess_client) of
          │ │ │ │ +logon(Name) ->
          │ │ │ │ +    case whereis(mess_client) of
          │ │ │ │          undefined ->
          │ │ │ │ -            register(mess_client,
          │ │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ │ +            register(mess_client,
          │ │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ │          _ -> already_logged_on
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -logoff() ->
          │ │ │ │ +logoff() ->
          │ │ │ │      mess_client ! logoff.
          │ │ │ │  
          │ │ │ │ -message(ToName, Message) ->
          │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
          │ │ │ │ +message(ToName, Message) ->
          │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
          │ │ │ │          undefined ->
          │ │ │ │              not_logged_on;
          │ │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ │               ok
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %%% The client process which runs on each user node
          │ │ │ │ -client(Server_Node, Name) ->
          │ │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ │ -    await_result(),
          │ │ │ │ -    client(Server_Node).
          │ │ │ │ +client(Server_Node, Name) ->
          │ │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ │ +    await_result(),
          │ │ │ │ +    client(Server_Node).
          │ │ │ │  
          │ │ │ │ -client(Server_Node) ->
          │ │ │ │ +client(Server_Node) ->
          │ │ │ │      receive
          │ │ │ │          logoff ->
          │ │ │ │ -            exit(normal);
          │ │ │ │ -        {message_to, ToName, Message} ->
          │ │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ │ -            await_result();
          │ │ │ │ -        {message_from, FromName, Message} ->
          │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ │ +            exit(normal);
          │ │ │ │ +        {message_to, ToName, Message} ->
          │ │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ │ +            await_result();
          │ │ │ │ +        {message_from, FromName, Message} ->
          │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ │      end,
          │ │ │ │ -    client(Server_Node).
          │ │ │ │ +    client(Server_Node).
          │ │ │ │  
          │ │ │ │  %%% wait for a response from the server
          │ │ │ │ -await_result() ->
          │ │ │ │ +await_result() ->
          │ │ │ │      receive
          │ │ │ │ -        {messenger, stop, Why} -> % Stop the client
          │ │ │ │ -            io:format("~p~n", [Why]),
          │ │ │ │ -            exit(normal);
          │ │ │ │ -        {messenger, What} ->  % Normal response
          │ │ │ │ -            io:format("~p~n", [What])
          │ │ │ │ +        {messenger, stop, Why} -> % Stop the client
          │ │ │ │ +            io:format("~p~n", [Why]),
          │ │ │ │ +            exit(normal);
          │ │ │ │ +        {messenger, What} ->  % Normal response
          │ │ │ │ +            io:format("~p~n", [What])
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("No response from server~n", []),
          │ │ │ │ -            exit(timeout)
          │ │ │ │ +            io:format("No response from server~n", []),
          │ │ │ │ +            exit(timeout)
          │ │ │ │      end.

          The following changes are added:

          The messenger server traps exits. If it receives an exit signal, │ │ │ │ {'EXIT',From,Reason}, this means that a client process has terminated or is │ │ │ │ unreachable for one of the following reasons:

          • The user has logged off (the "logoff" message is removed).
          • The network connection to the client is broken.
          • The node on which the client process resides has gone down.
          • The client processes has done some illegal operation.

          If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ │ the servers User_List using the server_logoff function. If the node on which │ │ │ │ the server runs goes down, an exit signal (automatically generated by the │ │ │ │ system) is sent to all of the client processes: │ │ │ │ {'EXIT',MessengerPID,noconnection} causing all the client processes to │ │ │ ├── OEBPS/release_structure.xhtml │ │ │ │ @@ -41,37 +41,37 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Release Resource File │ │ │ │

          │ │ │ │

          To define a release, create a release resource file, or in short a .rel │ │ │ │ file. In the file, specify the name and version of the release, which ERTS │ │ │ │ -version it is based on, and which applications it consists of:

          {release, {Name,Vsn}, {erts, EVsn},
          │ │ │ │ - [{Application1, AppVsn1},
          │ │ │ │ +version it is based on, and which applications it consists of:

          {release, {Name,Vsn}, {erts, EVsn},
          │ │ │ │ + [{Application1, AppVsn1},
          │ │ │ │     ...
          │ │ │ │ -  {ApplicationN, AppVsnN}]}.

          Name, Vsn, EVsn, and AppVsn are strings.

          The file must be named Rel.rel, where Rel is a unique name.

          Each Application (atom) and AppVsn is the name and version of an application │ │ │ │ + {ApplicationN, AppVsnN}]}.

          Name, Vsn, EVsn, and AppVsn are strings.

          The file must be named Rel.rel, where Rel is a unique name.

          Each Application (atom) and AppVsn is the name and version of an application │ │ │ │ included in the release. The minimal release based on Erlang/OTP consists of the │ │ │ │ Kernel and STDLIB applications, so these applications must be included in the │ │ │ │ list.

          If the release is to be upgraded, it must also include the SASL application.

          Here is an example showing the .app file for a release of ch_app from │ │ │ │ -the Applications section:

          {application, ch_app,
          │ │ │ │ - [{description, "Channel allocator"},
          │ │ │ │ -  {vsn, "1"},
          │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ -  {registered, [ch3]},
          │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ -  {mod, {ch_app,[]}}
          │ │ │ │ - ]}.

          The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ │ -applications are required by ch_app. The file is called ch_rel-1.rel:

          {release,
          │ │ │ │ - {"ch_rel", "A"},
          │ │ │ │ - {erts, "14.2.5"},
          │ │ │ │ - [{kernel, "9.2.4"},
          │ │ │ │ -  {stdlib, "5.2.3"},
          │ │ │ │ -  {sasl, "4.2.1"},
          │ │ │ │ -  {ch_app, "1"}]
          │ │ │ │ -}.

          │ │ │ │ +the Applications section:

          {application, ch_app,
          │ │ │ │ + [{description, "Channel allocator"},
          │ │ │ │ +  {vsn, "1"},
          │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ +  {registered, [ch3]},
          │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ +  {mod, {ch_app,[]}}
          │ │ │ │ + ]}.

          The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ │ +applications are required by ch_app. The file is called ch_rel-1.rel:

          {release,
          │ │ │ │ + {"ch_rel", "A"},
          │ │ │ │ + {erts, "14.2.5"},
          │ │ │ │ + [{kernel, "9.2.4"},
          │ │ │ │ +  {stdlib, "5.2.3"},
          │ │ │ │ +  {sasl, "4.2.1"},
          │ │ │ │ +  {ch_app, "1"}]
          │ │ │ │ +}.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Generating Boot Scripts │ │ │ │

          │ │ │ │

          systools in the SASL application includes tools to build and check │ │ │ │ releases. The functions read the .rel and .app files and perform │ │ │ │ @@ -95,17 +95,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Release Package │ │ │ │ │ │ │ │

          The systools:make_tar/1,2 function takes a │ │ │ │ .rel file as input and creates a zipped tar file with the code for │ │ │ │ -the specified applications, a release package:

          1> systools:make_script("ch_rel-1").
          │ │ │ │ +the specified applications, a release package:

          1> systools:make_script("ch_rel-1").
          │ │ │ │  ok
          │ │ │ │ -2> systools:make_tar("ch_rel-1").
          │ │ │ │ +2> systools:make_tar("ch_rel-1").
          │ │ │ │  ok

          The release package by default contains:

          • The .app files
          • The .rel file
          • The object code for all applications, structured according to the │ │ │ │ application directory structure
          • The binary boot script renamed to start.boot
          % tar tf ch_rel-1.tar
          │ │ │ │  lib/kernel-9.2.4/ebin/kernel.app
          │ │ │ │  lib/kernel-9.2.4/ebin/application.beam
          │ │ │ │  ...
          │ │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
          │ │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
          │ │ │ ├── OEBPS/release_handling.xhtml
          │ │ │ │ @@ -128,38 +128,38 @@
          │ │ │ │    update
          │ │ │ │  
          │ │ │ │  

          If a more complex change has been made, for example, a change to the format of │ │ │ │ the internal state of a gen_server, simple code replacement is not sufficient. │ │ │ │ Instead, it is necessary to:

          • Suspend the processes using the module (to avoid that they try to handle any │ │ │ │ requests before the code replacement is completed).
          • Ask them to transform the internal state format and switch to the new version │ │ │ │ of the module.
          • Remove the old version.
          • Resume the processes.

          This is called synchronized code replacement and for this the following │ │ │ │ -instructions are used:

          {update, Module, {advanced, Extra}}
          │ │ │ │ -{update, Module, supervisor}

          update with argument {advanced,Extra} is used when changing the internal │ │ │ │ +instructions are used:

          {update, Module, {advanced, Extra}}
          │ │ │ │ +{update, Module, supervisor}

          update with argument {advanced,Extra} is used when changing the internal │ │ │ │ state of a behaviour as described above. It causes behaviour processes to call │ │ │ │ the callback function code_change/3, passing the term Extra and some other │ │ │ │ information as arguments. See the manual pages for the respective behaviours and │ │ │ │ Appup Cookbook.

          update with argument supervisor is used when changing the start │ │ │ │ specification of a supervisor. See Appup Cookbook.

          When a module is to be updated, the release handler finds which processes that │ │ │ │ are using the module by traversing the supervision tree of each running │ │ │ │ -application and checking all the child specifications:

          {Id, StartFunc, Restart, Shutdown, Type, Modules}

          A process uses a module if the name is listed in Modules in the child │ │ │ │ +application and checking all the child specifications:

          {Id, StartFunc, Restart, Shutdown, Type, Modules}

          A process uses a module if the name is listed in Modules in the child │ │ │ │ specification for the process.

          If Modules=dynamic, which is the case for event managers, the event manager │ │ │ │ process informs the release handler about the list of currently installed event │ │ │ │ handlers (gen_event), and it is checked if the module name is in this list │ │ │ │ instead.

          The release handler suspends, asks for code change, and resumes processes by │ │ │ │ calling the functions sys:suspend/1,2, sys:change_code/4,5, and │ │ │ │ sys:resume/1,2, respectively.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ add_module and delete_module │ │ │ │

          │ │ │ │ -

          If a new module is introduced, the following instruction is used:

          {add_module, Module}

          This instruction loads module Module. When running Erlang in │ │ │ │ +

          If a new module is introduced, the following instruction is used:

          {add_module, Module}

          This instruction loads module Module. When running Erlang in │ │ │ │ embedded mode it is necessary to use this this instruction. It is not │ │ │ │ strictly required when running Erlang in interactive mode, since the │ │ │ │ -code server automatically searches for and loads unloaded modules.

          The opposite of add_module is delete_module, which unloads a module:

          {delete_module, Module}

          Any process, in any application, with Module as residence module, is │ │ │ │ +code server automatically searches for and loads unloaded modules.

          The opposite of add_module is delete_module, which unloads a module:

          {delete_module, Module}

          Any process, in any application, with Module as residence module, is │ │ │ │ killed when the instruction is evaluated. Therefore, the user must │ │ │ │ ensure that all such processes are terminated before deleting module │ │ │ │ Module to avoid a situation with failing supervisor restarts.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Instructions │ │ │ │ @@ -246,60 +246,60 @@ │ │ │ │ .app file.

        • Each UpFromVsn is a previous version of the application to upgrade from.
        • Each DownToVsn is a previous version of the application to downgrade to.
        • Each Instructions is a list of release handling instructions.

        UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ │ appup in SASL.

        Appup Cookbook includes examples of .appup files for │ │ │ │ typical upgrade/downgrade cases.

        Example: Consider the release ch_rel-1 from │ │ │ │ Releases. Assume you want to add a function │ │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ │ -directory, to ensure that the first version is still available):

        -module(ch3).
        │ │ │ │ --behaviour(gen_server).
        │ │ │ │ +directory, to ensure that the first version is still available):

        -module(ch3).
        │ │ │ │ +-behaviour(gen_server).
        │ │ │ │  
        │ │ │ │ --export([start_link/0]).
        │ │ │ │ --export([alloc/0, free/1]).
        │ │ │ │ --export([available/0]).
        │ │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
        │ │ │ │ +-export([start_link/0]).
        │ │ │ │ +-export([alloc/0, free/1]).
        │ │ │ │ +-export([available/0]).
        │ │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
        │ │ │ │  
        │ │ │ │ -start_link() ->
        │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
        │ │ │ │ +start_link() ->
        │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
        │ │ │ │  
        │ │ │ │ -alloc() ->
        │ │ │ │ -    gen_server:call(ch3, alloc).
        │ │ │ │ +alloc() ->
        │ │ │ │ +    gen_server:call(ch3, alloc).
        │ │ │ │  
        │ │ │ │ -free(Ch) ->
        │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).
        │ │ │ │ +free(Ch) ->
        │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).
        │ │ │ │  
        │ │ │ │ -available() ->
        │ │ │ │ -    gen_server:call(ch3, available).
        │ │ │ │ +available() ->
        │ │ │ │ +    gen_server:call(ch3, available).
        │ │ │ │  
        │ │ │ │ -init(_Args) ->
        │ │ │ │ -    {ok, channels()}.
        │ │ │ │ +init(_Args) ->
        │ │ │ │ +    {ok, channels()}.
        │ │ │ │  
        │ │ │ │ -handle_call(alloc, _From, Chs) ->
        │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
        │ │ │ │ -    {reply, Ch, Chs2};
        │ │ │ │ -handle_call(available, _From, Chs) ->
        │ │ │ │ -    N = available(Chs),
        │ │ │ │ -    {reply, N, Chs}.
        │ │ │ │ +handle_call(alloc, _From, Chs) ->
        │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
        │ │ │ │ +    {reply, Ch, Chs2};
        │ │ │ │ +handle_call(available, _From, Chs) ->
        │ │ │ │ +    N = available(Chs),
        │ │ │ │ +    {reply, N, Chs}.
        │ │ │ │  
        │ │ │ │ -handle_cast({free, Ch}, Chs) ->
        │ │ │ │ -    Chs2 = free(Ch, Chs),
        │ │ │ │ -    {noreply, Chs2}.

        A new version of the ch_app.app file must now be created, where the version is │ │ │ │ -updated:

        {application, ch_app,
        │ │ │ │ - [{description, "Channel allocator"},
        │ │ │ │ -  {vsn, "2"},
        │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
        │ │ │ │ -  {registered, [ch3]},
        │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
        │ │ │ │ -  {mod, {ch_app,[]}}
        │ │ │ │ - ]}.

        To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + Chs2 = free(Ch, Chs), │ │ │ │ + {noreply, Chs2}.

        A new version of the ch_app.app file must now be created, where the version is │ │ │ │ +updated:

        {application, ch_app,
        │ │ │ │ + [{description, "Channel allocator"},
        │ │ │ │ +  {vsn, "2"},
        │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
        │ │ │ │ +  {registered, [ch3]},
        │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
        │ │ │ │ +  {mod, {ch_app,[]}}
        │ │ │ │ + ]}.

        To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

        {"2",
        │ │ │ │ - [{"1", [{load_module, ch3}]}],
        │ │ │ │ - [{"1", [{load_module, ch3}]}]
        │ │ │ │ -}.

        │ │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

        {"2",
        │ │ │ │ + [{"1", [{load_module, ch3}]}],
        │ │ │ │ + [{"1", [{load_module, ch3}]}]
        │ │ │ │ +}.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │ │

        │ │ │ │

        To define how to upgrade/downgrade between the new version and previous versions │ │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ │ @@ -310,22 +310,22 @@ │ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ │ transformed into a single list of low-level instructions in the right order.

        If the relup file is relatively simple, it can be created manually. It is only │ │ │ │ to contain low-level instructions.

        For details about the syntax and contents of the release upgrade file, see │ │ │ │ relup in SASL.

        Example, continued from the previous section: You have a new version "2" of │ │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ │ -changed from "A" to "B":

        {release,
        │ │ │ │ - {"ch_rel", "B"},
        │ │ │ │ - {erts, "14.2.5"},
        │ │ │ │ - [{kernel, "9.2.4"},
        │ │ │ │ -  {stdlib, "5.2.3"},
        │ │ │ │ -  {sasl, "4.2.1"},
        │ │ │ │ -  {ch_app, "2"}]
        │ │ │ │ -}.

        Now the relup file can be generated:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
        │ │ │ │ +changed from "A" to "B":

        {release,
        │ │ │ │ + {"ch_rel", "B"},
        │ │ │ │ + {erts, "14.2.5"},
        │ │ │ │ + [{kernel, "9.2.4"},
        │ │ │ │ +  {stdlib, "5.2.3"},
        │ │ │ │ +  {sasl, "4.2.1"},
        │ │ │ │ +  {ch_app, "2"}]
        │ │ │ │ +}.

        Now the relup file can be generated:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
        │ │ │ │  ok

        This generates a relup file with instructions for how to upgrade from version │ │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ │ "B" to version "A".

        Both the old and new versions of the .app and .rel files must be in the code │ │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ │ extended by using the option path:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
        │ │ │ │  [{path,["../ch_rel-1",
        │ │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
        │ │ │ │ @@ -338,25 +338,25 @@
        │ │ │ │  

        When you have made a new version of a release, a release package can be created │ │ │ │ with this new version and transferred to the target environment.

        To install the new version of the release in runtime, the release │ │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ │ which handles unpacking, installation, and removal of release │ │ │ │ packages. The release_handler module communicates with this process.

        Assuming there is an operational target system with installation root directory │ │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ │ to $ROOT/releases.

        First, unpack the release package. The files are then extracted from the │ │ │ │ -package:

        release_handler:unpack_release(ReleaseName) => {ok, Vsn}
        • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ +package:

          release_handler:unpack_release(ReleaseName) => {ok, Vsn}
          • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ extension.
          • Vsn is the version of the unpacked release, as defined in its .rel file.

          A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ │ placed. For applications with new version numbers, the application directories │ │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

          An unpacked release can be installed. The release handler then evaluates the │ │ │ │ -instructions in relup, step by step:

          release_handler:install_release(Vsn) => {ok, FromVsn, []}

          If an error occurs during the installation, the system is rebooted using the old │ │ │ │ +instructions in relup, step by step:

          release_handler:install_release(Vsn) => {ok, FromVsn, []}

          If an error occurs during the installation, the system is rebooted using the old │ │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ │ the new version of the release, but if anything happens and the system is │ │ │ │ rebooted, it starts using the previous version again.

          To be made the default version, the newly installed release must be made │ │ │ │ permanent, which means the previous version becomes old:

          release_handler:make_permanent(Vsn) => ok

          The system keeps information about which versions are old and permanent in the │ │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

          To downgrade from Vsn to FromVsn, install_release must be called again:

          release_handler:install_release(FromVsn) => {ok, Vsn, []}

          An installed, but not permanent, release can be removed. Information about the │ │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

          To downgrade from Vsn to FromVsn, install_release must be called again:

          release_handler:install_release(FromVsn) => {ok, Vsn, []}

          An installed, but not permanent, release can be removed. Information about the │ │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ │ directory, are removed.

          release_handler:remove_release(Vsn) => ok

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ │ @@ -367,17 +367,17 @@ │ │ │ │ is needed, the file is to contain the empty list:

          [].

          Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ │ and config file is enough for illustration purposes:

          % cd $ROOT
          │ │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
          │ │ │ │  ...

          $ROOT is the installation directory of the target system.

          Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ │ sys.config and the relup file. For more information, see │ │ │ │ -Release Upgrade File.

          1> systools:make_script("ch_rel-2").
          │ │ │ │ +Release Upgrade File.

          1> systools:make_script("ch_rel-2").
          │ │ │ │  ok
          │ │ │ │ -2> systools:make_tar("ch_rel-2").
          │ │ │ │ +2> systools:make_tar("ch_rel-2").
          │ │ │ │  ok

          The new release package now also contains version "2" of ch_app and the │ │ │ │ relup file:

          % tar tf ch_rel-2.tar
          │ │ │ │  lib/kernel-9.2.4/ebin/kernel.app
          │ │ │ │  lib/kernel-9.2.4/ebin/application.beam
          │ │ │ │  ...
          │ │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
          │ │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
          │ │ │ │ @@ -390,31 +390,31 @@
          │ │ │ │  lib/ch_app-2/ebin/ch_sup.beam
          │ │ │ │  lib/ch_app-2/ebin/ch3.beam
          │ │ │ │  releases/B/start.boot
          │ │ │ │  releases/B/relup
          │ │ │ │  releases/B/sys.config
          │ │ │ │  releases/B/ch_rel-2.rel
          │ │ │ │  releases/ch_rel-2.rel

          Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ │ -directory.

          Step 5) In the running target system, unpack the release package:

          1> release_handler:unpack_release("ch_rel-2").
          │ │ │ │ -{ok,"B"}

          The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ +directory.

          Step 5) In the running target system, unpack the release package:

          1> release_handler:unpack_release("ch_rel-2").
          │ │ │ │ +{ok,"B"}

          The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ │ they have not changed.

          Under $ROOT/releases, a new directory B is created, containing │ │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

          Step 6) Check if the function ch3:available/0 is available:

          2> ch3:available().
          │ │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

          Step 6) Check if the function ch3:available/0 is available:

          2> ch3:available().
          │ │ │ │  ** exception error: undefined function ch3:available/0

          Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ │ -function ch3:available/0 is now available:

          3> release_handler:install_release("B").
          │ │ │ │ -{ok,"A",[]}
          │ │ │ │ -4> ch3:available().
          │ │ │ │ +function ch3:available/0 is now available:

          3> release_handler:install_release("B").
          │ │ │ │ +{ok,"A",[]}
          │ │ │ │ +4> ch3:available().
          │ │ │ │  3
          │ │ │ │ -5> code:which(ch3).
          │ │ │ │ +5> code:which(ch3).
          │ │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
          │ │ │ │ -6> code:which(ch_sup).
          │ │ │ │ +6> code:which(ch_sup).
          │ │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

          Processes in ch_app for which code have not been updated, for example, the │ │ │ │ supervisor, are still evaluating code from ch_app-1.

          Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

          7> release_handler:make_permanent("B").
          │ │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

          7> release_handler:make_permanent("B").
          │ │ │ │  ok

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │ │

          │ │ │ │

          When a new version of a release is installed, the application specifications are │ │ │ │ @@ -423,14 +423,14 @@ │ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ │ release package itself.

          Specifically, the application configuration parameters are automatically updated │ │ │ │ according to (in increasing priority order):

          • The data in the boot script, fetched from the new application resource file │ │ │ │ App.app
          • The new sys.config
          • Command-line arguments -App Par Val

          This means that parameter values set in the other system configuration files and │ │ │ │ values set using application:set_env/3 are disregarded.

          When an installed release is made permanent, the system process init is set to │ │ │ │ point out the new sys.config.

          After the installation, the application controller compares the old and new │ │ │ │ configuration parameters for all running applications and call the callback │ │ │ │ -function:

          Module:config_change(Changed, New, Removed)
          • Module is the application callback module as defined by the mod key in the │ │ │ │ +function:

            Module:config_change(Changed, New, Removed)
            • Module is the application callback module as defined by the mod key in the │ │ │ │ .app file.
            • Changed and New are lists of {Par,Val} for all changed and added │ │ │ │ configuration parameters, respectively.
            • Removed is a list of all parameters Par that have been removed.

            The function is optional and can be omitted when implementing an application │ │ │ │ callback module.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_records.xhtml │ │ │ │ @@ -28,17 +28,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │

          │ │ │ │

          A record definition consists of the name of the record, followed by the field │ │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ │ -used.

          -record(Name, {Field1 [= Expr1],
          │ │ │ │ +used.

          -record(Name, {Field1 [= Expr1],
          │ │ │ │                 ...
          │ │ │ │ -               FieldN [= ExprN]}).

          The default value for a field is an arbitrary expression, except that it must │ │ │ │ + FieldN [= ExprN]}).

          The default value for a field is an arbitrary expression, except that it must │ │ │ │ not use any variables.

          A record definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ record.

          If a record is used in several modules, it is recommended that the record │ │ │ │ definition is placed in an include file.

          Change

          Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ │ necessary to use the shell built-in function rd/2.

          │ │ │ │ │ │ │ │ @@ -48,32 +48,32 @@ │ │ │ │

          │ │ │ │

          The following expression creates a new Name record where the value of each │ │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

          #Name{Field1=Expr1, ..., FieldK=ExprK}

          The fields can be in any order, not necessarily the same order as in the record │ │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ │ default value instead.

          If several fields are to be assigned the same value, the following construction │ │ │ │ can be used:

          #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

          Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ │ -and Mnesia match functions.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +and Mnesia match functions.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -lookup(Name, Tab) ->
          │ │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

          │ │ │ │ +lookup(Name, Tab) -> │ │ │ │ + ets:match_object(Tab, #person{name=Name, _='_'}).

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │ │

          │ │ │ │
          Expr#Name.Field

          Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ │ -record.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +record.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -get_person_name(Person) ->
          │ │ │ │ +get_person_name(Person) ->
          │ │ │ │      Person#person.name.

          The following expression returns the position of the specified field in the │ │ │ │ -tuple representation of the record:

          #Name.Field

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +tuple representation of the record:

          #Name.Field

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -lookup(Name, List) ->
          │ │ │ │ -    lists:keyfind(Name, #person.name, List).

          │ │ │ │ +lookup(Name, List) -> │ │ │ │ + lists:keyfind(Name, #person.name, List).

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │ │

          │ │ │ │
          Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

          Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ │ @@ -83,48 +83,48 @@ │ │ │ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │

        │ │ │ │

        Since record expressions are expanded to tuple expressions, creating │ │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ │ -expressions as well.

        Examples:

        handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
        │ │ │ │ +expressions as well.

        Examples:

        handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
        │ │ │ │      ...
        │ │ │ │  
        │ │ │ │ -handle(Msg, State) when State#state.running =:= true ->
        │ │ │ │ -    ...

        There is also a type test BIF is_record(Term, RecordTag).

        Example:

        is_person(P) when is_record(P, person) ->
        │ │ │ │ +handle(Msg, State) when State#state.running =:= true ->
        │ │ │ │ +    ...

        There is also a type test BIF is_record(Term, RecordTag).

        Example:

        is_person(P) when is_record(P, person) ->
        │ │ │ │      true;
        │ │ │ │ -is_person(_P) ->
        │ │ │ │ +is_person(_P) ->
        │ │ │ │      false.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │ │

        │ │ │ │

        A pattern that matches a certain record is created in the same way as a record │ │ │ │ is created:

        #Name{Field1=Expr1, ..., FieldK=ExprK}

        In this case, one or more of Expr1 ... ExprK can be unbound variables.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

        │ │ │ │ -

        Assume the following record definitions:

        -record(nrec0, {name = "nested0"}).
        │ │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
        │ │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
        │ │ │ │ +

        Assume the following record definitions:

        -record(nrec0, {name = "nested0"}).
        │ │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
        │ │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
        │ │ │ │  
        │ │ │ │ -N2 = #nrec2{},

        Accessing or updating nested records can be written without parentheses:

        "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
        │ │ │ │ +N2 = #nrec2{},

        Accessing or updating nested records can be written without parentheses:

        "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
        │ │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

        which is equivalent to:

        "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
        │ │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

        Change

        Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ │ nested records.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │ │

        │ │ │ │

        Record expressions are translated to tuple expressions during compilation. A │ │ │ │ -record defined as:

        -record(Name, {Field1, ..., FieldN}).

        is internally represented by the tuple:

        {Name, Value1, ..., ValueN}

        Here each ValueI is the default value for FieldI.

        To each module using records, a pseudo function is added during compilation to │ │ │ │ -obtain information about records:

        record_info(fields, Record) -> [Field]
        │ │ │ │ -record_info(size, Record) -> Size

        Size is the size of the tuple representation, that is, one more than the │ │ │ │ +record defined as:

        -record(Name, {Field1, ..., FieldN}).

        is internally represented by the tuple:

        {Name, Value1, ..., ValueN}

        Here each ValueI is the default value for FieldI.

        To each module using records, a pseudo function is added during compilation to │ │ │ │ +obtain information about records:

        record_info(fields, Record) -> [Field]
        │ │ │ │ +record_info(size, Record) -> Size

        Size is the size of the tuple representation, that is, one more than the │ │ │ │ number of fields.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_processes.xhtml │ │ │ │ @@ -30,18 +30,18 @@ │ │ │ │ (grow and shrink dynamically) with small memory footprint, fast to create and │ │ │ │ terminate, and the scheduling overhead is low.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Process Creation │ │ │ │

        │ │ │ │ -

        A process is created by calling spawn():

        spawn(Module, Name, Args) -> pid()
        │ │ │ │ -  Module = Name = atom()
        │ │ │ │ -  Args = [Arg1,...,ArgN]
        │ │ │ │ -    ArgI = term()

        spawn() creates a new process and returns the pid.

        The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ +

        A process is created by calling spawn():

        spawn(Module, Name, Args) -> pid()
        │ │ │ │ +  Module = Name = atom()
        │ │ │ │ +  Args = [Arg1,...,ArgN]
        │ │ │ │ +    ArgI = term()

        spawn() creates a new process and returns the pid.

        The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ arguments are the elements of the (possible empty) Args argument list.

        There exist a number of different spawn BIFs:

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │ │

        │ │ │ │

        Besides addressing a process by using its pid, there are also BIFs for │ │ │ ├── OEBPS/ref_man_functions.xhtml │ │ │ │ @@ -25,51 +25,51 @@ │ │ │ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │ │ │

        A function declaration is a sequence of function clauses separated by │ │ │ │ semicolons, and terminated by a period (.).

        A function clause consists of a clause head and a clause body, separated by │ │ │ │ ->.

        A clause head consists of the function name, an argument list, and an optional │ │ │ │ -guard sequence beginning with the keyword when:

        Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
        │ │ │ │ +guard sequence beginning with the keyword when:

        Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
        │ │ │ │      Body1;
        │ │ │ │  ...;
        │ │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
        │ │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
        │ │ │ │      BodyK.

        The function name is an atom. Each argument is a pattern.

        The number of arguments N is the arity of the function. A function is │ │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ │ functions with the same name and in the same module, but with different arities │ │ │ │ are two different functions.

        A function named f in module mod and with arity N is often denoted as │ │ │ │ mod:f/N.

        A clause body consists of a sequence of expressions separated by comma (,):

        Expr1,
        │ │ │ │  ...,
        │ │ │ │  ExprN

        Valid Erlang expressions and guard sequences are described in │ │ │ │ -Expressions.

        Example:

        fact(N) when N > 0 ->  % first clause head
        │ │ │ │ -    N * fact(N-1);     % first clause body
        │ │ │ │ +Expressions.

        Example:

        fact(N) when N > 0 ->  % first clause head
        │ │ │ │ +    N * fact(N-1);     % first clause body
        │ │ │ │  
        │ │ │ │ -fact(0) ->             % second clause head
        │ │ │ │ +fact(0) ->             % second clause head
        │ │ │ │      1.                 % second clause body

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │ │

        │ │ │ │

        When a function M:F/N is called, first the code for the function is located. │ │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ │ the function must be exported to be visible outside the module it is defined in.

        If the function is found, the function clauses are scanned sequentially until a │ │ │ │ clause is found that fulfills both of the following two conditions:

        1. The patterns in the clause head can be successfully matched against the given │ │ │ │ arguments.
        2. The guard sequence, if any, is true.

        If such a clause cannot be found, a function_clause runtime error occurs.

        If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ │ -expression is returned.

        Consider the function fact:

        -module(mod).
        │ │ │ │ --export([fact/1]).
        │ │ │ │ +expression is returned.

        Consider the function fact:

        -module(mod).
        │ │ │ │ +-export([fact/1]).
        │ │ │ │  
        │ │ │ │ -fact(N) when N > 0 ->
        │ │ │ │ -    N * fact(N - 1);
        │ │ │ │ -fact(0) ->
        │ │ │ │ +fact(N) when N > 0 ->
        │ │ │ │ +    N * fact(N - 1);
        │ │ │ │ +fact(0) ->
        │ │ │ │      1.

        Assume that you want to calculate the factorial for 1:

        1> mod:fact(1).

        Evaluation starts at the first clause. The pattern N is matched against │ │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ │ -bound to 1, and the corresponding body is evaluated:

        N * fact(N-1) => (N is bound to 1)
        │ │ │ │ -1 * fact(0)

        Now, fact(0) is called, and the function clauses are scanned │ │ │ │ +bound to 1, and the corresponding body is evaluated:

        N * fact(N-1) => (N is bound to 1)
        │ │ │ │ +1 * fact(0)

        Now, fact(0) is called, and the function clauses are scanned │ │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ │ and the body is evaluated:

        1 * fact(0) =>
        │ │ │ │  1 * 1 =>
        │ │ │ │  1

        Evaluation has succeed and mod:fact(1) returns 1.

        If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ │ matches. A function_clause runtime error occurs.

        │ │ │ │ @@ -78,17 +78,17 @@ │ │ │ │ │ │ │ │ Tail recursion │ │ │ │

        │ │ │ │

        If the last expression of a function body is a function call, a │ │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ │ -stack and can (in principle) run forever.

        Example:

        loop(N) ->
        │ │ │ │ -    io:format("~w~n", [N]),
        │ │ │ │ -    loop(N+1).

        The earlier factorial example is a counter-example. It is not │ │ │ │ +stack and can (in principle) run forever.

        Example:

        loop(N) ->
        │ │ │ │ +    io:format("~w~n", [N]),
        │ │ │ │ +    loop(N+1).

        The earlier factorial example is a counter-example. It is not │ │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ │ call to fact(N-1).

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │ │

        │ │ │ │ @@ -96,14 +96,14 @@ │ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ │ and ets.

        The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ │ -without specifying the module name.

        Examples:

        1> tuple_size({a,b,c}).
        │ │ │ │ +without specifying the module name.

        Examples:

        1> tuple_size({a,b,c}).
        │ │ │ │  3
        │ │ │ │ -2> atom_to_list('Erlang').
        │ │ │ │ +2> atom_to_list('Erlang').
        │ │ │ │  "Erlang"
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/records_macros.xhtml │ │ │ │ @@ -29,40 +29,40 @@ │ │ │ │ │ │ │ │

        To illustrate this, the messenger example from the previous section is divided │ │ │ │ into the following five files:

        • mess_config.hrl

          Header file for configuration data

        • mess_interface.hrl

          Interface definitions between the client and the messenger

        • user_interface.erl

          Functions for the user interface

        • mess_client.erl

          Functions for the client side of the messenger

        • mess_server.erl

          Functions for the server side of the messenger

        While doing this, the message passing interface between the shell, the client, │ │ │ │ and the server is cleaned up and is defined using records. Also, macros are │ │ │ │ introduced:

        %%%----FILE mess_config.hrl----
        │ │ │ │  
        │ │ │ │  %%% Configure the location of the server node,
        │ │ │ │ --define(server_node, messenger@super).
        │ │ │ │ +-define(server_node, messenger@super).
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE mess_interface.hrl----
        │ │ │ │  
        │ │ │ │  %%% Message interface between client and server and client shell for
        │ │ │ │  %%% messenger program
        │ │ │ │  
        │ │ │ │  %%%Messages from Client to server received in server/1 function.
        │ │ │ │ --record(logon,{client_pid, username}).
        │ │ │ │ --record(message,{client_pid, to_name, message}).
        │ │ │ │ +-record(logon,{client_pid, username}).
        │ │ │ │ +-record(message,{client_pid, to_name, message}).
        │ │ │ │  %%% {'EXIT', ClientPid, Reason}  (client terminated or unreachable.
        │ │ │ │  
        │ │ │ │  %%% Messages from Server to Client, received in await_result/0 function
        │ │ │ │ --record(abort_client,{message}).
        │ │ │ │ +-record(abort_client,{message}).
        │ │ │ │  %%% Messages are: user_exists_at_other_node,
        │ │ │ │  %%%               you_are_not_logged_on
        │ │ │ │ --record(server_reply,{message}).
        │ │ │ │ +-record(server_reply,{message}).
        │ │ │ │  %%% Messages are: logged_on
        │ │ │ │  %%%               receiver_not_found
        │ │ │ │  %%%               sent  (Message has been sent (no guarantee)
        │ │ │ │  %%% Messages from Server to Client received in client/1 function
        │ │ │ │ --record(message_from,{from_name, message}).
        │ │ │ │ +-record(message_from,{from_name, message}).
        │ │ │ │  
        │ │ │ │  %%% Messages from shell to Client received in client/1 function
        │ │ │ │  %%% spawn(mess_client, client, [server_node(), Name])
        │ │ │ │ --record(message_to,{to_name, message}).
        │ │ │ │ +-record(message_to,{to_name, message}).
        │ │ │ │  %%% logoff
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE user_interface.erl----
        │ │ │ │  
        │ │ │ │  %%% User interface to the messenger program
        │ │ │ │  %%% login(Name)
        │ │ │ │  %%%     One user at a time can log in from each Erlang node in the
        │ │ │ │ @@ -75,177 +75,177 @@
        │ │ │ │  %%%     Logs off anybody at that node
        │ │ │ │  
        │ │ │ │  %%% message(ToName, Message)
        │ │ │ │  %%%     sends Message to ToName. Error messages if the user of this
        │ │ │ │  %%%     function is not logged on or if ToName is not logged on at
        │ │ │ │  %%%     any node.
        │ │ │ │  
        │ │ │ │ --module(user_interface).
        │ │ │ │ --export([logon/1, logoff/0, message/2]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ --include("mess_config.hrl").
        │ │ │ │ +-module(user_interface).
        │ │ │ │ +-export([logon/1, logoff/0, message/2]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +-include("mess_config.hrl").
        │ │ │ │  
        │ │ │ │ -logon(Name) ->
        │ │ │ │ -    case whereis(mess_client) of
        │ │ │ │ +logon(Name) ->
        │ │ │ │ +    case whereis(mess_client) of
        │ │ │ │          undefined ->
        │ │ │ │ -            register(mess_client,
        │ │ │ │ -                     spawn(mess_client, client, [?server_node, Name]));
        │ │ │ │ +            register(mess_client,
        │ │ │ │ +                     spawn(mess_client, client, [?server_node, Name]));
        │ │ │ │          _ -> already_logged_on
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │ -logoff() ->
        │ │ │ │ +logoff() ->
        │ │ │ │      mess_client ! logoff.
        │ │ │ │  
        │ │ │ │ -message(ToName, Message) ->
        │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
        │ │ │ │ +message(ToName, Message) ->
        │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
        │ │ │ │          undefined ->
        │ │ │ │              not_logged_on;
        │ │ │ │ -        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
        │ │ │ │ +        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
        │ │ │ │               ok
        │ │ │ │  end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE mess_client.erl----
        │ │ │ │  
        │ │ │ │  %%% The client process which runs on each user node
        │ │ │ │  
        │ │ │ │ --module(mess_client).
        │ │ │ │ --export([client/2]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ -
        │ │ │ │ -client(Server_Node, Name) ->
        │ │ │ │ -    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
        │ │ │ │ -    await_result(),
        │ │ │ │ -    client(Server_Node).
        │ │ │ │ +-module(mess_client).
        │ │ │ │ +-export([client/2]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +
        │ │ │ │ +client(Server_Node, Name) ->
        │ │ │ │ +    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
        │ │ │ │ +    await_result(),
        │ │ │ │ +    client(Server_Node).
        │ │ │ │  
        │ │ │ │ -client(Server_Node) ->
        │ │ │ │ +client(Server_Node) ->
        │ │ │ │      receive
        │ │ │ │          logoff ->
        │ │ │ │ -            exit(normal);
        │ │ │ │ -        #message_to{to_name=ToName, message=Message} ->
        │ │ │ │ -            {messenger, Server_Node} !
        │ │ │ │ -                #message{client_pid=self(), to_name=ToName, message=Message},
        │ │ │ │ -            await_result();
        │ │ │ │ -        {message_from, FromName, Message} ->
        │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
        │ │ │ │ +            exit(normal);
        │ │ │ │ +        #message_to{to_name=ToName, message=Message} ->
        │ │ │ │ +            {messenger, Server_Node} !
        │ │ │ │ +                #message{client_pid=self(), to_name=ToName, message=Message},
        │ │ │ │ +            await_result();
        │ │ │ │ +        {message_from, FromName, Message} ->
        │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
        │ │ │ │      end,
        │ │ │ │ -    client(Server_Node).
        │ │ │ │ +    client(Server_Node).
        │ │ │ │  
        │ │ │ │  %%% wait for a response from the server
        │ │ │ │ -await_result() ->
        │ │ │ │ +await_result() ->
        │ │ │ │      receive
        │ │ │ │ -        #abort_client{message=Why} ->
        │ │ │ │ -            io:format("~p~n", [Why]),
        │ │ │ │ -            exit(normal);
        │ │ │ │ -        #server_reply{message=What} ->
        │ │ │ │ -            io:format("~p~n", [What])
        │ │ │ │ +        #abort_client{message=Why} ->
        │ │ │ │ +            io:format("~p~n", [Why]),
        │ │ │ │ +            exit(normal);
        │ │ │ │ +        #server_reply{message=What} ->
        │ │ │ │ +            io:format("~p~n", [What])
        │ │ │ │      after 5000 ->
        │ │ │ │ -            io:format("No response from server~n", []),
        │ │ │ │ -            exit(timeout)
        │ │ │ │ +            io:format("No response from server~n", []),
        │ │ │ │ +            exit(timeout)
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE---
        %%%----FILE mess_server.erl----
        │ │ │ │  
        │ │ │ │  %%% This is the server process of the messenger service
        │ │ │ │  
        │ │ │ │ --module(mess_server).
        │ │ │ │ --export([start_server/0, server/0]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ -
        │ │ │ │ -server() ->
        │ │ │ │ -    process_flag(trap_exit, true),
        │ │ │ │ -    server([]).
        │ │ │ │ +-module(mess_server).
        │ │ │ │ +-export([start_server/0, server/0]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +
        │ │ │ │ +server() ->
        │ │ │ │ +    process_flag(trap_exit, true),
        │ │ │ │ +    server([]).
        │ │ │ │  
        │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
        │ │ │ │ -server(User_List) ->
        │ │ │ │ -    io:format("User list = ~p~n", [User_List]),
        │ │ │ │ +server(User_List) ->
        │ │ │ │ +    io:format("User list = ~p~n", [User_List]),
        │ │ │ │      receive
        │ │ │ │ -        #logon{client_pid=From, username=Name} ->
        │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
        │ │ │ │ -            server(New_User_List);
        │ │ │ │ -        {'EXIT', From, _} ->
        │ │ │ │ -            New_User_List = server_logoff(From, User_List),
        │ │ │ │ -            server(New_User_List);
        │ │ │ │ -        #message{client_pid=From, to_name=To, message=Message} ->
        │ │ │ │ -            server_transfer(From, To, Message, User_List),
        │ │ │ │ -            server(User_List)
        │ │ │ │ +        #logon{client_pid=From, username=Name} ->
        │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
        │ │ │ │ +            server(New_User_List);
        │ │ │ │ +        {'EXIT', From, _} ->
        │ │ │ │ +            New_User_List = server_logoff(From, User_List),
        │ │ │ │ +            server(New_User_List);
        │ │ │ │ +        #message{client_pid=From, to_name=To, message=Message} ->
        │ │ │ │ +            server_transfer(From, To, Message, User_List),
        │ │ │ │ +            server(User_List)
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%% Start the server
        │ │ │ │ -start_server() ->
        │ │ │ │ -    register(messenger, spawn(?MODULE, server, [])).
        │ │ │ │ +start_server() ->
        │ │ │ │ +    register(messenger, spawn(?MODULE, server, [])).
        │ │ │ │  
        │ │ │ │  %%% Server adds a new user to the user list
        │ │ │ │ -server_logon(From, Name, User_List) ->
        │ │ │ │ +server_logon(From, Name, User_List) ->
        │ │ │ │      %% check if logged on anywhere else
        │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
        │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
        │ │ │ │          true ->
        │ │ │ │ -            From ! #abort_client{message=user_exists_at_other_node},
        │ │ │ │ +            From ! #abort_client{message=user_exists_at_other_node},
        │ │ │ │              User_List;
        │ │ │ │          false ->
        │ │ │ │ -            From ! #server_reply{message=logged_on},
        │ │ │ │ -            link(From),
        │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
        │ │ │ │ +            From ! #server_reply{message=logged_on},
        │ │ │ │ +            link(From),
        │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%% Server deletes a user from the user list
        │ │ │ │ -server_logoff(From, User_List) ->
        │ │ │ │ -    lists:keydelete(From, 1, User_List).
        │ │ │ │ +server_logoff(From, User_List) ->
        │ │ │ │ +    lists:keydelete(From, 1, User_List).
        │ │ │ │  
        │ │ │ │  %%% Server transfers a message between user
        │ │ │ │ -server_transfer(From, To, Message, User_List) ->
        │ │ │ │ +server_transfer(From, To, Message, User_List) ->
        │ │ │ │      %% check that the user is logged on and who he is
        │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
        │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ -        {value, {_, Name}} ->
        │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
        │ │ │ │ +            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ +        {value, {_, Name}} ->
        │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
        │ │ │ │      end.
        │ │ │ │  %%% If the user exists, send the message
        │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
        │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
        │ │ │ │      %% Find the receiver and send the message
        │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
        │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ -        {value, {ToPid, To}} ->
        │ │ │ │ -            ToPid ! #message_from{from_name=Name, message=Message},
        │ │ │ │ -            From !  #server_reply{message=sent}
        │ │ │ │ +            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ +        {value, {ToPid, To}} ->
        │ │ │ │ +            ToPid ! #message_from{from_name=Name, message=Message},
        │ │ │ │ +            From !  #server_reply{message=sent}
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE---

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Header Files │ │ │ │

        │ │ │ │

        As shown above, some files have extension .hrl. These are header files that │ │ │ │ -are included in the .erl files by:

        -include("File_Name").

        for example:

        -include("mess_interface.hrl").

        In the case above the file is fetched from the same directory as all the other │ │ │ │ +are included in the .erl files by:

        -include("File_Name").

        for example:

        -include("mess_interface.hrl").

        In the case above the file is fetched from the same directory as all the other │ │ │ │ files in the messenger example. (manual).

        .hrl files can contain any valid Erlang code but are most often used for record │ │ │ │ and macro definitions.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │ │

        │ │ │ │ -

        A record is defined as:

        -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

        For example:

        -record(message_to,{to_name, message}).

        This is equivalent to:

        {message_to, To_Name, Message}

        Creating a record is best illustrated by an example:

        #message_to{message="hello", to_name=fred)

        This creates:

        {message_to, fred, "hello"}

        Notice that you do not have to worry about the order you assign values to the │ │ │ │ +

        A record is defined as:

        -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

        For example:

        -record(message_to,{to_name, message}).

        This is equivalent to:

        {message_to, To_Name, Message}

        Creating a record is best illustrated by an example:

        #message_to{message="hello", to_name=fred)

        This creates:

        {message_to, fred, "hello"}

        Notice that you do not have to worry about the order you assign values to the │ │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ │ creating a record, it gets the value of the atom undefined. (manual)

        Pattern matching with records is very similar to creating records. For example, │ │ │ │ -inside a case or receive:

        #message_to{to_name=ToName, message=Message} ->

        This is the same as:

        {message_to, ToName, Message}

        │ │ │ │ +inside a case or receive:

        #message_to{to_name=ToName, message=Message} ->

        This is the same as:

        {message_to, ToName, Message}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │ │

        │ │ │ │

        Another thing that has been added to the messenger is a macro. The file │ │ │ │ mess_config.hrl contains the definition:

        %%% Configure the location of the server node,
        │ │ │ │ --define(server_node, messenger@super).

        This file is included in mess_server.erl:

        -include("mess_config.hrl").

        Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ -messenger@super.

        A macro is also used when spawning the server process:

        spawn(?MODULE, server, [])

        This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ +-define(server_node, messenger@super).

        This file is included in mess_server.erl:

        -include("mess_config.hrl").

        Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ +messenger@super.

        A macro is also used when spawning the server process:

        spawn(?MODULE, server, [])

        This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ │ of using macros with, for example, parameters.

        The three Erlang (.erl) files in the messenger example are individually │ │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ │ these files into the system when they are referred to during execution of the │ │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ │ ├── OEBPS/prog_ex_records.xhtml │ │ │ │ @@ -27,105 +27,105 @@ │ │ │ │ Records and Tuples │ │ │ │ │ │ │ │

        The main advantage of using records rather than tuples is that fields in a │ │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ │ with the tuple {Name, Address, Phone}.

        To write functions that manipulate this data, remember the following:

        • The Name field is the first element of the tuple.
        • The Address field is the second element.
        • The Phone field is the third element.

        For example, to extract data from a variable P that contains such a tuple, you │ │ │ │ can write the following code and then use pattern matching to extract the │ │ │ │ -relevant fields:

        Name = element(1, P),
        │ │ │ │ -Address = element(2, P),
        │ │ │ │ +relevant fields:

        Name = element(1, P),
        │ │ │ │ +Address = element(2, P),
        │ │ │ │  ...

        Such code is difficult to read and understand, and errors occur if the numbering │ │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ │ person tuple must be checked and possibly modified.

        Records allow references to the fields by name, instead of by position. In the │ │ │ │ -following example, a record instead of a tuple is used to store the data:

        -record(person, {name, phone, address}).

        This enables references to the fields of the record by name. For example, if P │ │ │ │ +following example, a record instead of a tuple is used to store the data:

        -record(person, {name, phone, address}).

        This enables references to the fields of the record by name. For example, if P │ │ │ │ is a variable whose value is a person record, the following code access the │ │ │ │ name and address fields of the records:

        Name = P#person.name,
        │ │ │ │  Address = P#person.address,
        │ │ │ │ -...

        Internally, records are represented using tagged tuples:

        {person, Name, Phone, Address}

        │ │ │ │ +...

        Internally, records are represented using tagged tuples:

        {person, Name, Phone, Address}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │ │

        │ │ │ │

        This following definition of a person is used in several examples in this │ │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ │ -field:

        -record(person, {name = "", phone = [], address}).

        The record must be defined in the shell to enable use of the record syntax in │ │ │ │ -the examples:

        > rd(person, {name = "", phone = [], address}).
        │ │ │ │ +field:

        -record(person, {name = "", phone = [], address}).

        The record must be defined in the shell to enable use of the record syntax in │ │ │ │ +the examples:

        > rd(person, {name = "", phone = [], address}).
        │ │ │ │  person

        This is because record definitions are only available at compile time, not at │ │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ │ STDLIB.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │ │

        │ │ │ │ -

        A new person record is created as follows:

        > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
        │ │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

        As the address field was omitted, its default value is used.

        From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ -special field _. _ means "all fields not explicitly specified".

        Example:

        > #person{name = "Jakob", _ = '_'}.
        │ │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

        It is primarily intended to be used in ets:match/2 and │ │ │ │ +

        A new person record is created as follows:

        > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
        │ │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

        As the address field was omitted, its default value is used.

        From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ +special field _. _ means "all fields not explicitly specified".

        Example:

        > #person{name = "Jakob", _ = '_'}.
        │ │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

        It is primarily intended to be used in ets:match/2 and │ │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ │ wildcard in ets:match/2.)

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │ │

        │ │ │ │ -

        The following example shows how to access a record field:

        > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
        │ │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
        │ │ │ │ +

        The following example shows how to access a record field:

        > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
        │ │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
        │ │ │ │  > P#person.name.
        │ │ │ │  "Joe"

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │ │

        │ │ │ │ -

        The following example shows how to update a record:

        > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
        │ │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
        │ │ │ │ -> P2 = P1#person{name="Robert"}.
        │ │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

        │ │ │ │ +

        The following example shows how to update a record:

        > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
        │ │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
        │ │ │ │ +> P2 = P1#person{name="Robert"}.
        │ │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │ │

        │ │ │ │

        The following example shows that the guard succeeds if P is record of type │ │ │ │ -person:

        foo(P) when is_record(P, person) -> a_person;
        │ │ │ │ -foo(_) -> not_a_person.

        │ │ │ │ +person:

        foo(P) when is_record(P, person) -> a_person;
        │ │ │ │ +foo(_) -> not_a_person.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │ │

        │ │ │ │

        Matching can be used in combination with records, as shown in the following │ │ │ │ -example:

        > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
        │ │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
        │ │ │ │ -> #person{name = Name} = P3, Name.
        │ │ │ │ +example:

        > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
        │ │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
        │ │ │ │ +> #person{name = Name} = P3, Name.
        │ │ │ │  "Joe"

        The following function takes a list of person records and searches for the │ │ │ │ -phone number of a person with a particular name:

        find_phone([#person{name=Name, phone=Phone} | _], Name) ->
        │ │ │ │ -    {found,  Phone};
        │ │ │ │ -find_phone([_| T], Name) ->
        │ │ │ │ -    find_phone(T, Name);
        │ │ │ │ -find_phone([], Name) ->
        │ │ │ │ +phone number of a person with a particular name:

        find_phone([#person{name=Name, phone=Phone} | _], Name) ->
        │ │ │ │ +    {found,  Phone};
        │ │ │ │ +find_phone([_| T], Name) ->
        │ │ │ │ +    find_phone(T, Name);
        │ │ │ │ +find_phone([], Name) ->
        │ │ │ │      not_found.

        The fields referred to in the pattern can be given in any order.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

        │ │ │ │

        The value of a field in a record can be an instance of a record. Retrieval of │ │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ │ -example:

        -record(name, {first = "Robert", last = "Ericsson"}).
        │ │ │ │ --record(person, {name = #name{}, phone}).
        │ │ │ │ +example:

        -record(name, {first = "Robert", last = "Ericsson"}).
        │ │ │ │ +-record(person, {name = #name{}, phone}).
        │ │ │ │  
        │ │ │ │ -demo() ->
        │ │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
        │ │ │ │ -  First = (P#person.name)#name.first.

        Here, demo() evaluates to "Robert".

        │ │ │ │ +demo() -> │ │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ │ + First = (P#person.name)#name.first.

        Here, demo() evaluates to "Robert".

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │ │

        │ │ │ │

        Comments are embedded in the following example:

        %% File: person.hrl
        │ │ │ │  
        │ │ │ │ @@ -135,44 +135,44 @@
        │ │ │ │  %%    name:  A string (default is undefined).
        │ │ │ │  %%    age:   An integer (default is undefined).
        │ │ │ │  %%    phone: A list of integers (default is []).
        │ │ │ │  %%    dict:  A dictionary containing various information
        │ │ │ │  %%           about the person.
        │ │ │ │  %%           A {Key, Value} list (default is the empty list).
        │ │ │ │  %%------------------------------------------------------------
        │ │ │ │ --record(person, {name, age, phone = [], dict = []}).
        -module(person).
        │ │ │ │ --include("person.hrl").
        │ │ │ │ --compile(export_all). % For test purposes only.
        │ │ │ │ +-record(person, {name, age, phone = [], dict = []}).
        -module(person).
        │ │ │ │ +-include("person.hrl").
        │ │ │ │ +-compile(export_all). % For test purposes only.
        │ │ │ │  
        │ │ │ │  %% This creates an instance of a person.
        │ │ │ │  %%   Note: The phone number is not supplied so the
        │ │ │ │  %%         default value [] will be used.
        │ │ │ │  
        │ │ │ │ -make_hacker_without_phone(Name, Age) ->
        │ │ │ │ -   #person{name = Name, age = Age,
        │ │ │ │ -           dict = [{computer_knowledge, excellent},
        │ │ │ │ -                   {drinks, coke}]}.
        │ │ │ │ +make_hacker_without_phone(Name, Age) ->
        │ │ │ │ +   #person{name = Name, age = Age,
        │ │ │ │ +           dict = [{computer_knowledge, excellent},
        │ │ │ │ +                   {drinks, coke}]}.
        │ │ │ │  
        │ │ │ │  %% This demonstrates matching in arguments
        │ │ │ │  
        │ │ │ │ -print(#person{name = Name, age = Age,
        │ │ │ │ -              phone = Phone, dict = Dict}) ->
        │ │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
        │ │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
        │ │ │ │ +print(#person{name = Name, age = Age,
        │ │ │ │ +              phone = Phone, dict = Dict}) ->
        │ │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
        │ │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
        │ │ │ │  
        │ │ │ │  %% Demonstrates type testing, selector, updating.
        │ │ │ │  
        │ │ │ │ -birthday(P) when is_record(P, person) ->
        │ │ │ │ -   P#person{age = P#person.age + 1}.
        │ │ │ │ +birthday(P) when is_record(P, person) ->
        │ │ │ │ +   P#person{age = P#person.age + 1}.
        │ │ │ │  
        │ │ │ │ -register_two_hackers() ->
        │ │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
        │ │ │ │ -   OldHacker = birthday(Hacker1),
        │ │ │ │ +register_two_hackers() ->
        │ │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
        │ │ │ │ +   OldHacker = birthday(Hacker1),
        │ │ │ │     % The central_register_server should have
        │ │ │ │     % an interface function for this.
        │ │ │ │ -   central_register_server ! {register_person, Hacker1},
        │ │ │ │ -   central_register_server ! {register_person,
        │ │ │ │ -             OldHacker#person{name = "Robert",
        │ │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
        │ │ │ │ +
        central_register_server ! {register_person, Hacker1}, │ │ │ │ + central_register_server ! {register_person, │ │ │ │ + OldHacker#person{name = "Robert", │ │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/patterns.xhtml │ │ │ │ @@ -33,16 +33,16 @@ │ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ │ fails, an exception is raised.

        Examples:

        1> X.
        │ │ │ │  ** 1:1: variable 'X' is unbound **
        │ │ │ │  2> X = 2.
        │ │ │ │  2
        │ │ │ │  3> X + 1.
        │ │ │ │  3
        │ │ │ │ -4> {X, Y} = {1, 2}.
        │ │ │ │ +4> {X, Y} = {1, 2}.
        │ │ │ │  ** exception error: no match of right hand side value {1,2}
        │ │ │ │ -5> {X, Y} = {2, 3}.
        │ │ │ │ -{2,3}
        │ │ │ │ +5> {X, Y} = {2, 3}.
        │ │ │ │ +{2,3}
        │ │ │ │  6> Y.
        │ │ │ │  3
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/otp-patch-apply.xhtml │ │ │ │ @@ -106,13 +106,13 @@ │ │ │ │ │ │ │ │ Sanity check │ │ │ │ │ │ │ │

        The application dependencies can be checked using the Erlang shell. │ │ │ │ Application dependencies are verified among installed applications by │ │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ │ By calling system_information:sanity_check() one can validate │ │ │ │ -dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │ │ +dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │ │  ok

        Please take a look at the reference of sanity_check() for more │ │ │ │ information.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/opaques.xhtml │ │ │ │ @@ -27,24 +27,24 @@ │ │ │ │ Opaque Type Aliases │ │ │ │ │ │ │ │

        The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ │ the rest is up to convention.

        This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ │ the example of the sets:set() data type. This type was │ │ │ │ -defined in the sets module like this:

        -opaque set(Element) :: #set{segs :: segs(Element)}.

        OTP 24 changed the definition to the following in │ │ │ │ -this commit.

        -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

        And this change was safer and more backwards-compatible than if the type had │ │ │ │ +defined in the sets module like this:

        -opaque set(Element) :: #set{segs :: segs(Element)}.

        OTP 24 changed the definition to the following in │ │ │ │ +this commit.

        -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

        And this change was safer and more backwards-compatible than if the type had │ │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ │ on the definition of the type: no other modules should rely on the definition.

        This means that code that pattern-matched on set as a record/tuple technically │ │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ │ -error:

        case sets:new() of
        │ │ │ │ -    Set when is_tuple(Set) ->
        │ │ │ │ -        io:format("ok")
        │ │ │ │ +error:

        case sets:new() of
        │ │ │ │ +    Set when is_tuple(Set) ->
        │ │ │ │ +        io:format("ok")
        │ │ │ │  end.

        When working with an opaque defined in another module, here are some │ │ │ │ recommendations:

        • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ │ that reveal the type, such as tuple_size/1 .
        • Instead, use functions provided by the module for working with the type. For │ │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ │ sets:is_element/2, and so on.
        • sets:set(a) is a subtype of sets:set(a | b) and not the │ │ │ │ other way around. Generally, you can rely on the property that the_opaque(T) │ │ │ │ is a subtype of the_opaque(U) when T is a subtype of U.

        When defining your own opaques, here are some recommendations:

        • Since consumers are expected to not rely on the definition of the opaque type, │ │ │ ├── OEBPS/nif.xhtml │ │ │ │ @@ -38,26 +38,26 @@ │ │ │ │ Erlang Program │ │ │ │ │ │ │ │

          Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ │ two reasons:

          • The NIF library must be explicitly loaded by Erlang code in the same module.
          • All NIFs of a module must have an Erlang implementation as well.

          Normally these are minimal stub implementations that throw an exception. But │ │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ │ native implementations on some architectures.

          NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ │ -passed on to the library and used for initialization:

          -module(complex6).
          │ │ │ │ --export([foo/1, bar/1]).
          │ │ │ │ --nifs([foo/1, bar/1]).
          │ │ │ │ --on_load(init/0).
          │ │ │ │ -
          │ │ │ │ -init() ->
          │ │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
          │ │ │ │ -
          │ │ │ │ -foo(_X) ->
          │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).
          │ │ │ │ -bar(_Y) ->
          │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).

          Here, the directive on_load is used to get function init to be automatically │ │ │ │ +passed on to the library and used for initialization:

          -module(complex6).
          │ │ │ │ +-export([foo/1, bar/1]).
          │ │ │ │ +-nifs([foo/1, bar/1]).
          │ │ │ │ +-on_load(init/0).
          │ │ │ │ +
          │ │ │ │ +init() ->
          │ │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
          │ │ │ │ +
          │ │ │ │ +foo(_X) ->
          │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).
          │ │ │ │ +bar(_Y) ->
          │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).

          Here, the directive on_load is used to get function init to be automatically │ │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ │ unloaded and calls to functions within it, fail.

          Loading the NIF library overrides the stub implementations and cause calls to │ │ │ │ foo and bar to be dispatched to the NIF implementations instead.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -114,22 +114,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

          │ │ │ │

          Step 1. Compile the C code:

          unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
          │ │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

          Step 2: Start Erlang and compile the Erlang code:

          > erl
          │ │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
          │ │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
          │ │ │ │  
          │ │ │ │ -Eshell V5.7.5  (abort with ^G)
          │ │ │ │ -1> c(complex6).
          │ │ │ │ -{ok,complex6}

          Step 3: Run the example:

          3> complex6:foo(3).
          │ │ │ │ +Eshell V5.7.5  (abort with ^G)
          │ │ │ │ +1> c(complex6).
          │ │ │ │ +{ok,complex6}

          Step 3: Run the example:

          3> complex6:foo(3).
          │ │ │ │  4
          │ │ │ │ -4> complex6:bar(5).
          │ │ │ │ +4> complex6:bar(5).
          │ │ │ │  10
          │ │ │ │ -5> complex6:foo("not an integer").
          │ │ │ │ +5> complex6:foo("not an integer").
          │ │ │ │  ** exception error: bad argument
          │ │ │ │       in function  complex6:foo/1
          │ │ │ │          called as comlpex6:foo("not an integer")
          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/modules.xhtml │ │ │ │ @@ -23,20 +23,20 @@ │ │ │ │

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │ │

          │ │ │ │

          Erlang code is divided into modules. A module consists of a sequence of │ │ │ │ -attributes and function declarations, each terminated by a period (.).

          Example:

          -module(m).          % module attribute
          │ │ │ │ --export([fact/1]).   % module attribute
          │ │ │ │ +attributes and function declarations, each terminated by a period (.).

          Example:

          -module(m).          % module attribute
          │ │ │ │ +-export([fact/1]).   % module attribute
          │ │ │ │  
          │ │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
          │ │ │ │ -    N * fact(N-1);   %  |
          │ │ │ │ -fact(0) ->           %  |
          │ │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
          │ │ │ │ +    N * fact(N-1);   %  |
          │ │ │ │ +fact(0) ->           %  |
          │ │ │ │      1.               % end of function declaration

          For a description of function declarations, see │ │ │ │ Function Declaration Syntax.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │ │

          │ │ │ │ @@ -81,71 +81,71 @@ │ │ │ │ meaning.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │ │

        │ │ │ │

        It is possible to specify that the module is the callback module for a │ │ │ │ -behaviour:

        -behaviour(Behaviour).

        The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ +behaviour:

        -behaviour(Behaviour).

        The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

        • gen_server
        • gen_statem
        • gen_event
        • supervisor

        The spelling behavior is also accepted.

        The callback functions of the module can be specified either directly by the │ │ │ │ -exported function behaviour_info/1:

        behaviour_info(callbacks) -> Callbacks.

        or by a -callback attribute for each callback function:

        -callback Name(Arguments) -> Result.

        Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ +exported function behaviour_info/1:

        behaviour_info(callbacks) -> Callbacks.

        or by a -callback attribute for each callback function:

        -callback Name(Arguments) -> Result.

        Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ │ produce documentation or find discrepancies.

        Read more about behaviours and callback modules in │ │ │ │ OTP Design Principles.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │ │

        │ │ │ │ -

        The same syntax as for module attributes is used for record definitions:

        -record(Record, Fields).

        Record definitions are allowed anywhere in a module, also among the function │ │ │ │ +

        The same syntax as for module attributes is used for record definitions:

        -record(Record, Fields).

        Record definitions are allowed anywhere in a module, also among the function │ │ │ │ declarations. Read more in Records.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │ │

        │ │ │ │

        The same syntax as for module attributes is used by the preprocessor, which │ │ │ │ -supports file inclusion, macros, and conditional compilation:

        -include("SomeFile.hrl").
        │ │ │ │ --define(Macro, Replacement).

        Read more in Preprocessor.

        │ │ │ │ +supports file inclusion, macros, and conditional compilation:

        -include("SomeFile.hrl").
        │ │ │ │ +-define(Macro, Replacement).

        Read more in Preprocessor.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │ │

        │ │ │ │

        The same syntax as for module attributes is used for changing the pre-defined │ │ │ │ -macros ?FILE and ?LINE:

        -file(File, Line).

        This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ +macros ?FILE and ?LINE:

        -file(File, Line).

        This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ source program is generated by another tool. It also indicates the │ │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ │ which the source program is produced.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │ │

        │ │ │ │

        A similar syntax as for module attributes is used for specifying types and │ │ │ │ -function specifications:

        -type my_type() :: atom() | integer().
        │ │ │ │ --spec my_function(integer()) -> integer().

        Read more in Types and Function specifications.

        The description is based on │ │ │ │ +function specifications:

        -type my_type() :: atom() | integer().
        │ │ │ │ +-spec my_function(integer()) -> integer().

        Read more in Types and Function specifications.

        The description is based on │ │ │ │ EEP8 - Types and function specifications, │ │ │ │ which is not to be further updated.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │ │

        │ │ │ │

        The module attribute -doc(Documentation) is used to provide user documentation │ │ │ │ -for a function/type/callback:

        -doc("Example documentation").
        │ │ │ │ -example() -> ok.

        The attribute should be placed just before the entity it documents.The │ │ │ │ +for a function/type/callback:

        -doc("Example documentation").
        │ │ │ │ +example() -> ok.

        The attribute should be placed just before the entity it documents.The │ │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ │ Documentation are:

        • literal string or │ │ │ │ utf-8 encoded binary string - The string │ │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ │ triple quoted strings and │ │ │ │ sigils that translate to literal strings can be used. │ │ │ │ -The following examples are equivalent:

          -doc("Example \"docs\"").
          │ │ │ │ --doc(<<"Example \"docs\""/utf8>>).
          │ │ │ │ +The following examples are equivalent:

          -doc("Example \"docs\"").
          │ │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
          │ │ │ │  -doc ~S/Example "docs"/.
          │ │ │ │  -doc """
          │ │ │ │     Example "docs"
          │ │ │ │     """
          │ │ │ │  -doc ~B|Example "docs"|.

          For clarity it is recommended to use either normal "strings" or triple │ │ │ │ quoted strings for documentation attributes.

        • {file, file:name/0 } - Read the contents of filename and use │ │ │ │ that as the documentation string.

        • false - Set the current entity as hidden, that is, it should not be │ │ │ │ @@ -158,15 +158,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │ │ │ │ │ │

          While not a module attribute, but rather a directive (since it might affect │ │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ │ -features.

          The syntax is similar to that of an attribute, but has two arguments:

          -feature(FeatureName, enable | disable).

          Note that the feature directive can only appear │ │ │ │ +features.

          The syntax is similar to that of an attribute, but has two arguments:

          -feature(FeatureName, enable | disable).

          Note that the feature directive can only appear │ │ │ │ in a prefix of the module.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │ │

          │ │ │ │

          Comments can be placed anywhere in a module except within strings and │ │ │ ├── OEBPS/maps.xhtml │ │ │ │ @@ -53,16 +53,16 @@ │ │ │ │ single function that constructs the map using the map syntax and always use │ │ │ │ it.

        • Always update the map using the := operator (that is, requiring that an │ │ │ │ element with that key already exists). The := operator is slightly more │ │ │ │ efficient, and it helps catching mispellings of keys.

        • Whenever possible, match multiple map elements at once.

        • Whenever possible, update multiple map elements at once.

        • Avoid default values and the maps:get/3 function. If there are default │ │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ │ values in one go.

        • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ │ -efficiently add multiple default values. For example:

          DefaultMap = #{shoe_size => 42, editor => emacs},
          │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

        │ │ │ │ +efficiently add multiple default values. For example:

        DefaultMap = #{shoe_size => 42, editor => emacs},
        │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │ │

      │ │ │ │

      Using a map as a dictionary implies the following usage pattern:

      • Keys are usually variables not known at compile-time.
      • There can be any number of elements in the map.
      • Usually, no more than one element is looked up or updated at once.

      Given that usage pattern, the difference in performance between using the map │ │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ │ @@ -72,18 +72,18 @@ │ │ │ │ choice.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │ │

    │ │ │ │

    Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ │ -Examples:

    1> sets:new([{version,2}]).
    │ │ │ │ -#{}
    │ │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ │ -#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ │ +Examples:

    1> sets:new([{version,2}]).
    │ │ │ │ +#{}
    │ │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ │ +#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ │ few possible exceptions:

    • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ │ be a better choice than sets.
    • If the intersection operation is frequently used and operations that operate │ │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ │ gb_sets can potentially be a better choice than sets.
    • If the elements of the set are integers in a fairly compact range, the set can │ │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ │ @@ -108,18 +108,18 @@ │ │ │ │ for the runtime system).

    • N - The number of elements in the map.

    • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ │ sorted.

    • Value1 - The value corresponding to the first key in the key tuple.

    • ValueN - The value corresponding to the last key in the key tuple.

    As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ │ represented:

    01234
    FLATMAP2{a,z}foobar

    Table: #{a => foo, z => bar}

    Let us update the map: M#{q => baz}. The map now looks like this:

    012345
    FLATMAP3{a,q,z}foobazbar

    Table: #{a => foo, q => baz, z => bar}

    Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ │ like this:

    012345
    FLATMAP3{a,q,z}foobazbird

    Table: #{a => foo, q => baz, z => bird}

    When the value for an existing key is updated, the key tuple is not updated, │ │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ │ -For example:

    new() ->
    │ │ │ │ -    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ +For example:

    new() ->
    │ │ │ │ +    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ │ -new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ +new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ │ left to right.)

    The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ │ See Memory for more information about memory sizes.

    │ │ │ │ │ │ │ │ @@ -146,21 +146,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │ │

    │ │ │ │

    Using the map syntax is usually slightly more efficient than using the │ │ │ │ corresponding function in the maps module.

    The gain in efficiency for the map syntax is more noticeable for the following │ │ │ │ -operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ │ -Map3 = maps:update(y, Y, Map2),
    │ │ │ │ -Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ -right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ -left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ │ -Map3 = Map2#{Key2 := Y},
    │ │ │ │ -Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ +operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ │ +Map3 = maps:update(y, Y, Map2),
    │ │ │ │ +Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ +right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ +left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ │ +Map3 = Map2#{Key2 := Y},
    │ │ │ │ +Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ efficient than using the => operator for a small map.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │ │

    │ │ │ │

    Here follows some notes about most of the functions in the maps module. For │ │ │ │ @@ -211,23 +211,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │ │ │

    As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ │ code similar to the following:

    Result = case Map of
    │ │ │ │ -             #{Key := Value} -> Value;
    │ │ │ │ -             #{} -> Default
    │ │ │ │ +             #{Key := Value} -> Value;
    │ │ │ │ +             #{} -> Default
    │ │ │ │           end

    This is reasonably efficient, but if a small map is used as an alternative to │ │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ │ not storing default values in the map.

    If default values are nevertheless required, instead of calling maps:get/3 │ │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ │ -map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ │ +map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ │ default values.

    Change

    Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ │ longer be traced.

    │ │ │ │ │ │ │ ├── OEBPS/macros.xhtml │ │ │ │ @@ -22,56 +22,56 @@ │ │ │ │

    │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ File Inclusion │ │ │ │

    │ │ │ │ -

    A file can be included as follows:

    -include(File).
    │ │ │ │ --include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ │ +

    A file can be included as follows:

    -include(File).
    │ │ │ │ +-include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ │ as is, at the position of the directive.

    Include files are typically used for record and macro definitions that are │ │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ │ .hrl for include files.

    File can start with a path component $VAR, for some string VAR. If that is │ │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ │ $VAR is left as is.

    If the filename File is absolute (possibly after variable substitution), the │ │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ │ searched for in the following directories, and in this order:

    1. The current working directory
    2. The directory where the module is being compiled
    3. The directories given by the include option

    For details, see erlc in ERTS and │ │ │ │ -compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ │ --include("incdir/my_records.hrl").
    │ │ │ │ --include("/home/user/proj/my_records.hrl").
    │ │ │ │ --include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ +compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ │ +-include("incdir/my_records.hrl").
    │ │ │ │ +-include("/home/user/proj/my_records.hrl").
    │ │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ │ -assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ +assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ │ the file file.hrl.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │ │

    │ │ │ │ -

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ │ --define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ │ +

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ │ +-define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ macro.

    If a macro is used in several modules, it is recommended that the macro │ │ │ │ definition is placed in an include file.

    A macro is used as follows:

    ?Const
    │ │ │ │  ?Func(Arg1,...,ArgN)

    Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ │ -Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │ │ +Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │ │  ...
    │ │ │ │ -call(Request) ->
    │ │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ │ -    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ +call(Request) -> │ │ │ │ + server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ │ +    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ │ -corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │ │ +corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │ │  ...
    │ │ │ │ -bar(X) ->
    │ │ │ │ -    ?MACRO1(a, b),
    │ │ │ │ -    ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ │ -    {a,a,b,b},
    │ │ │ │ -    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ +bar(X) -> │ │ │ │ + ?MACRO1(a, b), │ │ │ │ + ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ │ +    {a,a,b,b},
    │ │ │ │ +    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ definition is a valid Erlang syntactic form.

    To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ │ after preprocessing and parse transforms, in the file File.P.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ │ @@ -90,29 +90,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │ │

    │ │ │ │

    It is possible to overload macros, except for predefined macros. An overloaded │ │ │ │ macro has more than one definition, each with a different number of arguments.

    Change

    Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

    A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ │ in an error message if there is at least one definition of Func with │ │ │ │ -arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ │ --define(F1(A), A).
    │ │ │ │ --define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │ │ +arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ │ +-define(F1(A), A).
    │ │ │ │ +-define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │ │      ?F0. % No, an empty list of arguments expected.
    │ │ │ │  
    │ │ │ │ -f1(A) ->
    │ │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ │ -    ?C().

    is expanded to

    f() ->
    │ │ │ │ -    m:f().

    │ │ │ │ +f1(A) -> │ │ │ │ + ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ │ +    ?C().

    is expanded to

    f() ->
    │ │ │ │ +    m:f().

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │ │

    │ │ │ │ -

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ │ +

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │ │

    │ │ │ │

    The following macro directives support conditional compilation:

    • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ │ defined.

    • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ │ @@ -124,43 +124,43 @@ │ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ │ are evaluated instead.

    • -endif. - Specifies the end of a series of control flow directives.

    Note

    Macro directives cannot be used inside functions.

    Syntactically, the Condition in if and elif must be a │ │ │ │ guard expression. Other constructs (such as │ │ │ │ a case expression) result in a compilation error.

    As opposed to the standard guard expressions, an expression in an if and │ │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ --ifdef(debug).
    │ │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │ │ +-ifdef(debug).
    │ │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │ │  -else.
    │ │ │ │ --define(LOG(X), true).
    │ │ │ │ +-define(LOG(X), true).
    │ │ │ │  -endif.
    │ │ │ │  
    │ │ │ │  ...

    When trace output is desired, debug is to be defined when the module m is │ │ │ │ compiled:

    % erlc -Ddebug m.erl
    │ │ │ │  
    │ │ │ │  or
    │ │ │ │  
    │ │ │ │ -1> c(m, {d, debug}).
    │ │ │ │ -{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ -with some simple trace output.

    Example:

    -module(m)
    │ │ │ │ +1> c(m, {d, debug}).
    │ │ │ │ +{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ +with some simple trace output.

    Example:

    -module(m)
    │ │ │ │  ...
    │ │ │ │ --if(?OTP_RELEASE >= 25).
    │ │ │ │ +-if(?OTP_RELEASE >= 25).
    │ │ │ │  %% Code that will work in OTP 25 or higher
    │ │ │ │ --elif(?OTP_RELEASE >= 26).
    │ │ │ │ +-elif(?OTP_RELEASE >= 26).
    │ │ │ │  %% Code that will work in OTP 26 or higher
    │ │ │ │  -else.
    │ │ │ │  %% Code that will work in OTP 24 or lower.
    │ │ │ │  -endif.
    │ │ │ │  ...

    This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ │ -release.

    Example:

    -module(m)
    │ │ │ │ +release.

    Example:

    -module(m)
    │ │ │ │  ...
    │ │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │ │  %% Debugging code that requires OTP 26 or later.
    │ │ │ │  -else.
    │ │ │ │  %% Non-debug code that works in any release.
    │ │ │ │  -endif.
    │ │ │ │  ...

    This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ │ code only for OTP 26 or later.

    │ │ │ │ │ │ │ │ @@ -175,40 +175,40 @@ │ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ │ definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │ │

    │ │ │ │ -

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ │ --export([version/0]).
    │ │ │ │ +

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ │ +-export([version/0]).
    │ │ │ │  
    │ │ │ │ --ifdef(VERSION).
    │ │ │ │ -version() -> ?VERSION.
    │ │ │ │ +-ifdef(VERSION).
    │ │ │ │ +version() -> ?VERSION.
    │ │ │ │  -else.
    │ │ │ │ --error("Macro VERSION must be defined.").
    │ │ │ │ -version() -> "".
    │ │ │ │ +-error("Macro VERSION must be defined.").
    │ │ │ │ +version() -> "".
    │ │ │ │  -endif.

    The error message will look like this:

    % erlc t.erl
    │ │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ │ --export([version/0]).
    │ │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ │ +-export([version/0]).
    │ │ │ │  
    │ │ │ │ --ifndef(VERSION).
    │ │ │ │ --warning("Macro VERSION not defined -- using default version.").
    │ │ │ │ --define(VERSION, "0").
    │ │ │ │ +-ifndef(VERSION).
    │ │ │ │ +-warning("Macro VERSION not defined -- using default version.").
    │ │ │ │ +-define(VERSION, "0").
    │ │ │ │  -endif.
    │ │ │ │ -version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │ │ +version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

    Change

    The -error() and -warning() directives were added in Erlang/OTP 19.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │ │

    │ │ │ │

    The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ │ -stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │ │ +stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │ │  
    │ │ │ │ -?TESTCALL(myfunction(1,2)),
    │ │ │ │ -?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │ │ +
    ?TESTCALL(myfunction(1,2)), │ │ │ │ +?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/listhandling.xhtml │ │ │ │ @@ -25,101 +25,101 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │

    │ │ │ │

    Lists can only be built starting from the end and attaching list elements at the │ │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ │ is a copy of the elements in List1, followed by List2:

    List1 ++ List2

    Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ │ -clearly the first list is copied:

    append([H|T], Tail) ->
    │ │ │ │ -    [H|append(T, Tail)];
    │ │ │ │ -append([], Tail) ->
    │ │ │ │ +clearly the first list is copied:

    append([H|T], Tail) ->
    │ │ │ │ +    [H|append(T, Tail)];
    │ │ │ │ +append([], Tail) ->
    │ │ │ │      Tail.

    When recursing and building a list, it is important to ensure that you attach │ │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ │ -list, not hundreds or thousands of copies of the growing result list.

    Let us first see how it is not to be done:

    DO NOT

    bad_fib(N) ->
    │ │ │ │ -    bad_fib(N, 0, 1, []).
    │ │ │ │ +list, not hundreds or thousands of copies of the growing result list.

    Let us first see how it is not to be done:

    DO NOT

    bad_fib(N) ->
    │ │ │ │ +    bad_fib(N, 0, 1, []).
    │ │ │ │  
    │ │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
    │ │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
    │ │ │ │      Fibs;
    │ │ │ │ -bad_fib(N, Current, Next, Fibs) ->
    │ │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

    Here more than one list is built. In each iteration step a new list is created │ │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

    Here more than one list is built. In each iteration step a new list is created │ │ │ │ that is one element longer than the new previous list.

    To avoid copying the result in each iteration, build the list in reverse order │ │ │ │ -and reverse the list when you are done:

    DO

    tail_recursive_fib(N) ->
    │ │ │ │ -    tail_recursive_fib(N, 0, 1, []).
    │ │ │ │ +and reverse the list when you are done:

    DO

    tail_recursive_fib(N) ->
    │ │ │ │ +    tail_recursive_fib(N, 0, 1, []).
    │ │ │ │  
    │ │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
    │ │ │ │ -    lists:reverse(Fibs);
    │ │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
    │ │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

    │ │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ │ + lists:reverse(Fibs); │ │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │ │

    │ │ │ │ -

    A list comprehension:

    [Expr(E) || E <- List]

    is basically translated to a local function:

    'lc^0'([E|Tail], Expr) ->
    │ │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
    │ │ │ │ -'lc^0'([], _Expr) -> [].

    If the result of the list comprehension will obviously not be used, a list │ │ │ │ -will not be constructed. For example, in this code:

    [io:put_chars(E) || E <- List],
    │ │ │ │ +

    A list comprehension:

    [Expr(E) || E <- List]

    is basically translated to a local function:

    'lc^0'([E|Tail], Expr) ->
    │ │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
    │ │ │ │ +'lc^0'([], _Expr) -> [].

    If the result of the list comprehension will obviously not be used, a list │ │ │ │ +will not be constructed. For example, in this code:

    [io:put_chars(E) || E <- List],
    │ │ │ │  ok.

    or in this code:

    case Var of
    │ │ │ │      ... ->
    │ │ │ │ -        [io:put_chars(E) || E <- List];
    │ │ │ │ +        [io:put_chars(E) || E <- List];
    │ │ │ │      ... ->
    │ │ │ │  end,
    │ │ │ │ -some_function(...),

    the value is not assigned to a variable, not passed to another function, and not │ │ │ │ +some_function(...),

    the value is not assigned to a variable, not passed to another function, and not │ │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ │ -will simplify the code for the list comprehension to:

    'lc^0'([E|Tail], Expr) ->
    │ │ │ │ -    Expr(E),
    │ │ │ │ -    'lc^0'(Tail, Expr);
    │ │ │ │ -'lc^0'([], _Expr) -> [].

    The compiler also understands that assigning to _ means that the value will │ │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

    _ = [io:put_chars(E) || E <- List],
    │ │ │ │ +will simplify the code for the list comprehension to:

    'lc^0'([E|Tail], Expr) ->
    │ │ │ │ +    Expr(E),
    │ │ │ │ +    'lc^0'(Tail, Expr);
    │ │ │ │ +'lc^0'([], _Expr) -> [].

    The compiler also understands that assigning to _ means that the value will │ │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

    _ = [io:put_chars(E) || E <- List],
    │ │ │ │  ok.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │ │

    │ │ │ │

    lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ │ but not its right argument).

    In the following situations it is unnecessary to call lists:flatten/1:

    • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ │ to flatten the list before sending it to the port.
    • When calling BIFs that accept deep lists, such as │ │ │ │ list_to_binary/1 or │ │ │ │ iolist_to_binary/1.
    • When you know that your list is only one level deep. Use lists:append/1 │ │ │ │ -instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ │ -port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ │ -port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ │ -[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ │ -[1,2,3]

    │ │ │ │ +instead.

Examples:

DO

port_command(Port, DeepList)

DO NOT

port_command(Port, lists:flatten(DeepList))

A common way to send a zero-terminated string to a port is the following:

DO NOT

TerminatedStr = String ++ [0],
│ │ │ │ +port_command(Port, TerminatedStr)

Instead:

DO

TerminatedStr = [String, 0],
│ │ │ │ +port_command(Port, TerminatedStr)

DO

1> lists:append([[1], [2], [3]]).
│ │ │ │ +[1,2,3]

DO NOT

1> lists:flatten([[1], [2], [3]]).
│ │ │ │ +[1,2,3]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │ │

│ │ │ │

There are two basic ways to write a function that traverses a list and │ │ │ │ produces a new list.

The first way is writing a body-recursive function:

%% Add 42 to each integer in the list.
│ │ │ │ -add_42_body([H|T]) ->
│ │ │ │ -    [H + 42 | add_42_body(T)];
│ │ │ │ -add_42_body([]) ->
│ │ │ │ -    [].

The second way is writing a tail-recursive function:

%% Add 42 to each integer in the list.
│ │ │ │ -add_42_tail(List) ->
│ │ │ │ -    add_42_tail(List, []).
│ │ │ │ +add_42_body([H|T]) ->
│ │ │ │ +    [H + 42 | add_42_body(T)];
│ │ │ │ +add_42_body([]) ->
│ │ │ │ +    [].

The second way is writing a tail-recursive function:

%% Add 42 to each integer in the list.
│ │ │ │ +add_42_tail(List) ->
│ │ │ │ +    add_42_tail(List, []).
│ │ │ │  
│ │ │ │ -add_42_tail([H|T], Acc) ->
│ │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
│ │ │ │ -add_42_tail([], Acc) ->
│ │ │ │ -    lists:reverse(Acc).

In early version of Erlang the tail-recursive function would typically │ │ │ │ +add_42_tail([H|T], Acc) -> │ │ │ │ + add_42_tail(T, [H + 42 | Acc]); │ │ │ │ +add_42_tail([], Acc) -> │ │ │ │ + lists:reverse(Acc).

In early version of Erlang the tail-recursive function would typically │ │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ │ much difference in performance between a body-recursive list function and │ │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ │ measure before rewriting your code.

For a thorough discussion about tail and body recursion, see │ │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

Note

This section is about list functions that construct lists. A tail-recursive │ │ │ │ function that does not construct a list runs in constant space, while the │ │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ │ length of the list.

For example, a function that sums a list of integers, is not to be written as │ │ │ │ -follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ -recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │ +follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ +recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │  
│ │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
│ │ │ │ -sum([], Sum)    -> Sum.
│ │ │ │ +
sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ │ +sum([], Sum) -> Sum.
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/list_comprehensions.xhtml │ │ │ │ @@ -22,33 +22,33 @@ │ │ │ │ │ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Examples │ │ │ │

│ │ │ │ -

This section starts with a simple example, showing a generator and a filter:

> [X || X <- [1,2,a,3,4,b,5,6], X > 3].
│ │ │ │ -[a,4,b,5,6]

This is read as follows: The list of X such that X is taken from the list │ │ │ │ +

This section starts with a simple example, showing a generator and a filter:

> [X || X <- [1,2,a,3,4,b,5,6], X > 3].
│ │ │ │ +[a,4,b,5,6]

This is read as follows: The list of X such that X is taken from the list │ │ │ │ [1,2,a,...] and X is greater than 3.

The notation X <- [1,2,a,...] is a generator and the expression X > 3 is a │ │ │ │ filter.

An additional filter, is_integer(X), can be added to │ │ │ │ -restrict the result to integers:

> [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
│ │ │ │ -[4,5,6]

Generators can be combined. For example, the Cartesian product of two lists can │ │ │ │ -be written as follows:

> [{X, Y} || X <- [1,2,3], Y <- [a,b]].
│ │ │ │ -[{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]

│ │ │ │ +restrict the result to integers:

> [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
│ │ │ │ +[4,5,6]

Generators can be combined. For example, the Cartesian product of two lists can │ │ │ │ +be written as follows:

> [{X, Y} || X <- [1,2,3], Y <- [a,b]].
│ │ │ │ +[{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Quick Sort │ │ │ │

│ │ │ │ -

The well-known quick sort routine can be written as follows:

sort([]) -> [];
│ │ │ │ -sort([_] = L) -> L;
│ │ │ │ -sort([Pivot|T]) ->
│ │ │ │ -    sort([ X || X <- T, X < Pivot]) ++
│ │ │ │ -    [Pivot] ++
│ │ │ │ -    sort([ X || X <- T, X >= Pivot]).

The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ │ +

The well-known quick sort routine can be written as follows:

sort([]) -> [];
│ │ │ │ +sort([_] = L) -> L;
│ │ │ │ +sort([Pivot|T]) ->
│ │ │ │ +    sort([ X || X <- T, X < Pivot]) ++
│ │ │ │ +    [Pivot] ++
│ │ │ │ +    sort([ X || X <- T, X >= Pivot]).

The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ │ that are less than Pivot.

[X || X <- T, X >= Pivot] is the list of all elements in T that are greater │ │ │ │ than or equal to Pivot.

With the algorithm above, a list is sorted as follows:

  • A list with zero or one element is trivially sorted.
  • For lists with more than one element:
    1. The first element in the list is isolated as the pivot element.
    2. The remaining list is partitioned into two sublists, such that:
    • The first sublist contains all elements that are smaller than the pivot │ │ │ │ element.
    • The second sublist contains all elements that are greater than or equal to │ │ │ │ the pivot element.
    1. The sublists are recursively sorted by the same algorithm and the results │ │ │ │ are combined, resulting in a list consisting of:
    • All elements from the first sublist, that is all elements smaller than the │ │ │ │ pivot element, in sorted order.
    • The pivot element.
    • All elements from the second sublist, that is all elements greater than or │ │ │ │ equal to the pivot element, in sorted order.

Note

While the sorting algorithm as shown above serves as a nice example to │ │ │ │ @@ -56,90 +56,90 @@ │ │ │ │ lists module contains sorting functions that are implemented in a more │ │ │ │ efficient way.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Permutations │ │ │ │

│ │ │ │ -

The following example generates all permutations of the elements in a list:

perms([]) -> [[]];
│ │ │ │ -perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

This takes H from L in all possible ways. The result is the set of all lists │ │ │ │ +

The following example generates all permutations of the elements in a list:

perms([]) -> [[]];
│ │ │ │ +perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

This takes H from L in all possible ways. The result is the set of all lists │ │ │ │ [H|T], where T is the set of all possible permutations of L, with H │ │ │ │ -removed:

> perms([b,u,g]).
│ │ │ │ -[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

│ │ │ │ +removed:

> perms([b,u,g]).
│ │ │ │ +[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Pythagorean Triplets │ │ │ │

│ │ │ │

Pythagorean triplets are sets of integers {A,B,C} such that │ │ │ │ A**2 + B**2 = C**2.

The function pyth(N) generates a list of all integers {A,B,C} such that │ │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ │ -N:

pyth(N) ->
│ │ │ │ -    [ {A,B,C} ||
│ │ │ │ -        A <- lists:seq(1,N),
│ │ │ │ -        B <- lists:seq(1,N),
│ │ │ │ -        C <- lists:seq(1,N),
│ │ │ │ +N:

pyth(N) ->
│ │ │ │ +    [ {A,B,C} ||
│ │ │ │ +        A <- lists:seq(1,N),
│ │ │ │ +        B <- lists:seq(1,N),
│ │ │ │ +        C <- lists:seq(1,N),
│ │ │ │          A+B+C =< N,
│ │ │ │          A*A+B*B == C*C
│ │ │ │ -    ].
> pyth(3).
│ │ │ │ -[].
│ │ │ │ -> pyth(11).
│ │ │ │ -[].
│ │ │ │ -> pyth(12).
│ │ │ │ -[{3,4,5},{4,3,5}]
│ │ │ │ -> pyth(50).
│ │ │ │ -[{3,4,5},
│ │ │ │ - {4,3,5},
│ │ │ │ - {5,12,13},
│ │ │ │ - {6,8,10},
│ │ │ │ - {8,6,10},
│ │ │ │ - {8,15,17},
│ │ │ │ - {9,12,15},
│ │ │ │ - {12,5,13},
│ │ │ │ - {12,9,15},
│ │ │ │ - {12,16,20},
│ │ │ │ - {15,8,17},
│ │ │ │ - {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ -   [{A,B,C} ||
│ │ │ │ -       A <- lists:seq(1,N-2),
│ │ │ │ -       B <- lists:seq(A+1,N-1),
│ │ │ │ -       C <- lists:seq(B+1,N),
│ │ │ │ +    ].
> pyth(3).
│ │ │ │ +[].
│ │ │ │ +> pyth(11).
│ │ │ │ +[].
│ │ │ │ +> pyth(12).
│ │ │ │ +[{3,4,5},{4,3,5}]
│ │ │ │ +> pyth(50).
│ │ │ │ +[{3,4,5},
│ │ │ │ + {4,3,5},
│ │ │ │ + {5,12,13},
│ │ │ │ + {6,8,10},
│ │ │ │ + {8,6,10},
│ │ │ │ + {8,15,17},
│ │ │ │ + {9,12,15},
│ │ │ │ + {12,5,13},
│ │ │ │ + {12,9,15},
│ │ │ │ + {12,16,20},
│ │ │ │ + {15,8,17},
│ │ │ │ + {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ +   [{A,B,C} ||
│ │ │ │ +       A <- lists:seq(1,N-2),
│ │ │ │ +       B <- lists:seq(A+1,N-1),
│ │ │ │ +       C <- lists:seq(B+1,N),
│ │ │ │         A+B+C =< N,
│ │ │ │ -       A*A+B*B == C*C ].

│ │ │ │ + A*A+B*B == C*C ].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │ │

│ │ │ │

As an example, list comprehensions can be used to simplify some of the functions │ │ │ │ -in lists.erl:

append(L)   ->  [X || L1 <- L, X <- L1].
│ │ │ │ -map(Fun, L) -> [Fun(X) || X <- L].
│ │ │ │ -filter(Pred, L) -> [X || X <- L, Pred(X)].

│ │ │ │ +in lists.erl:

append(L)   ->  [X || L1 <- L, X <- L1].
│ │ │ │ +map(Fun, L) -> [Fun(X) || X <- L].
│ │ │ │ +filter(Pred, L) -> [X || X <- L, Pred(X)].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │ │

│ │ │ │

The scope rules for variables that occur in list comprehensions are as follows:

  • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ │ variables.
  • Any variables that are defined before the list comprehension, and that are │ │ │ │ used in filters, have the values they had before the list comprehension.
  • Variables cannot be exported from a list comprehension.

As an example of these rules, suppose you want to write the function select, │ │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ │ tuples from L, where the first item is X.

Compiling this gives the following diagnostic:

./FileName.erl:Line: Warning: variable 'X' shadowed in generate

This diagnostic warns that the variable X in the pattern is not the same as │ │ │ │ -the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ -written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ -the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ -same name bound in a previous generator pattern. For example:

> [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
│ │ │ │ -[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ +the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ +written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ +the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ +same name bound in a previous generator pattern. For example:

> [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
│ │ │ │ +[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ │ -cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │ +cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ -    ...

Instead, write as follows:

f(...) ->
│ │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ +    ...

Instead, write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │      ...
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/install-win32.xhtml │ │ │ │ @@ -200,15 +200,15 @@ │ │ │ │
$ cd erts/emulator │ │ │ │ $ make debug │ │ │ │ $ cd ../etc │ │ │ │ $ make debug │ │ │ │

and sometimes

$ cd $ERL_TOP
│ │ │ │  $ make local_setup
│ │ │ │  

So now when you run $ERL_TOP/erl.exe, you should have a debug compiled │ │ │ │ -emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ +emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ got a debug compiled emulator.

To hack the erlang libraries, you simply do a make opt in the │ │ │ │ specific "applications" directory, like:

$ cd $ERL_TOP/lib/stdlib
│ │ │ │  $ make opt
│ │ │ │  

or even in the source directory...

$ cd $ERL_TOP/lib/stdlib/src
│ │ │ │  $ make opt
│ │ │ │  

Note that you're expected to have a fresh Erlang in your path when │ │ │ │ doing this, preferably the plain 27 you have built in the previous │ │ │ │ @@ -223,19 +223,19 @@ │ │ │ │ :$ERL_TOP/erts/etc/win32/wsl_tools:$ERL_TOP/bootstrap/bin:$PATH │ │ │ │

That should make it possible to rebuild any library without hassle...

If you want to copy a library (an application) newly built, to a │ │ │ │ release area, you do like with the emulator:

$ cd $ERL_TOP/lib/stdlib
│ │ │ │  $ make TESTROOT=/tmp/erlang_release release
│ │ │ │  

Remember that:

  • Windows specific C-code goes in the $ERL_TOP/erts/emulator/sys/win32, │ │ │ │ $ERL_TOP/erts/emulator/drivers/win32 or $ERL_TOP/erts/etc/win32.

  • Windows specific erlang code should be used conditionally and the │ │ │ │ host OS tested in runtime, the exactly same beam files should be │ │ │ │ -distributed for every platform! So write code like:

    case os:type() of
    │ │ │ │ -    {win32,_} ->
    │ │ │ │ -        do_windows_specific();
    │ │ │ │ +distributed for every platform! So write code like:

    case os:type() of
    │ │ │ │ +    {win32,_} ->
    │ │ │ │ +        do_windows_specific();
    │ │ │ │      Other ->
    │ │ │ │ -        do_fallback_or_exit()
    │ │ │ │ +        do_fallback_or_exit()
    │ │ │ │  end,

That's basically all you need to get going.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Frequently Asked Questions │ │ │ │

│ │ │ │
  • Q: So, now I can build Erlang using GCC on Windows?

    A: No, unfortunately not. You'll need Microsoft's Visual C++ │ │ │ ├── OEBPS/included_applications.xhtml │ │ │ │ @@ -66,72 +66,72 @@ │ │ │ │ belonging to the primary application.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Specifying Included Applications │ │ │ │

    │ │ │ │

    Which applications to include is defined by the included_applications key in │ │ │ │ -the .app file:

    {application, prim_app,
    │ │ │ │ - [{description, "Tree application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ -  {registered, [prim_app_server]},
    │ │ │ │ -  {included_applications, [incl_app]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {prim_app_cb,[]}},
    │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ - ]}.

    │ │ │ │ +the .app file:

    {application, prim_app,
    │ │ │ │ + [{description, "Tree application"},
    │ │ │ │ +  {vsn, "1"},
    │ │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ +  {registered, [prim_app_server]},
    │ │ │ │ +  {included_applications, [incl_app]},
    │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ +  {mod, {prim_app_cb,[]}},
    │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ + ]}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Synchronizing Processes during Startup │ │ │ │

    │ │ │ │

    The supervisor tree of an included application is started as part of the │ │ │ │ supervisor tree of the including application. If there is a need for │ │ │ │ synchronization between processes in the including and included applications, │ │ │ │ this can be achieved by using start phases.

    Start phases are defined by the start_phases key in the .app file as a list │ │ │ │ of tuples {Phase,PhaseArgs}, where Phase is an atom and PhaseArgs is a │ │ │ │ term.

    The value of the mod key of the including application must be set to │ │ │ │ {application_starter,[Module,StartArgs]}, where Module as usual is the │ │ │ │ application callback module. StartArgs is a term provided as argument to the │ │ │ │ -callback function Module:start/2:

    {application, prim_app,
    │ │ │ │ - [{description, "Tree application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ -  {registered, [prim_app_server]},
    │ │ │ │ -  {included_applications, [incl_app]},
    │ │ │ │ -  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ - ]}.
    │ │ │ │ +callback function Module:start/2:

    {application, prim_app,
    │ │ │ │ + [{description, "Tree application"},
    │ │ │ │ +  {vsn, "1"},
    │ │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ +  {registered, [prim_app_server]},
    │ │ │ │ +  {included_applications, [incl_app]},
    │ │ │ │ +  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ +  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ + ]}.
    │ │ │ │  
    │ │ │ │ -{application, incl_app,
    │ │ │ │ - [{description, "Included application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [incl_app_cb, incl_app_sup, incl_app_server]},
    │ │ │ │ -  {registered, []},
    │ │ │ │ -  {start_phases, [{go,[]}]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {incl_app_cb,[]}}
    │ │ │ │ - ]}.

    When starting a primary application with included applications, the primary │ │ │ │ +{application, incl_app, │ │ │ │ + [{description, "Included application"}, │ │ │ │ + {vsn, "1"}, │ │ │ │ + {modules, [incl_app_cb, incl_app_sup, incl_app_server]}, │ │ │ │ + {registered, []}, │ │ │ │ + {start_phases, [{go,[]}]}, │ │ │ │ + {applications, [kernel, stdlib, sasl]}, │ │ │ │ + {mod, {incl_app_cb,[]}} │ │ │ │ + ]}.

    When starting a primary application with included applications, the primary │ │ │ │ application is started the normal way, that is:

    • The application controller creates an application master for the application
    • The application master calls Module:start(normal, StartArgs) to start the │ │ │ │ top supervisor.

    Then, for the primary application and each included application in top-down, │ │ │ │ left-to-right order, the application master calls │ │ │ │ Module:start_phase(Phase, Type, PhaseArgs) for each phase defined for the │ │ │ │ primary application, in that order. If a phase is not defined for an included │ │ │ │ application, the function is not called for this phase and application.

    The following requirements apply to the .app file for an included application:

    • The {mod, {Module,StartArgs}} option must be included. This option is used │ │ │ │ to find the callback module Module of the application. StartArgs is │ │ │ │ ignored, as Module:start/2 is called only for the primary application.
    • If the included application itself contains included applications, instead the │ │ │ │ {mod, {application_starter, [Module,StartArgs]}} option must be included.
    • The {start_phases, [{Phase,PhaseArgs}]} option must be included, and the set │ │ │ │ of specified phases must be a subset of the set of phases specified for the │ │ │ │ primary application.

    When starting prim_app as defined above, the application controller calls the │ │ │ │ following callback functions before application:start(prim_app) returns a │ │ │ │ -value:

    application:start(prim_app)
    │ │ │ │ - => prim_app_cb:start(normal, [])
    │ │ │ │ - => prim_app_cb:start_phase(init, normal, [])
    │ │ │ │ - => prim_app_cb:start_phase(go, normal, [])
    │ │ │ │ - => incl_app_cb:start_phase(go, normal, [])
    │ │ │ │ +value:

    application:start(prim_app)
    │ │ │ │ + => prim_app_cb:start(normal, [])
    │ │ │ │ + => prim_app_cb:start_phase(init, normal, [])
    │ │ │ │ + => prim_app_cb:start_phase(go, normal, [])
    │ │ │ │ + => incl_app_cb:start_phase(go, normal, [])
    │ │ │ │  ok
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/gen_server_concepts.xhtml │ │ │ │ @@ -62,63 +62,63 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │ │ │ │ │

    An example of a simple server written in plain Erlang is provided in │ │ │ │ Overview. The server can be reimplemented using │ │ │ │ -gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ │ --behaviour(gen_server).
    │ │ │ │ +gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ │ +-behaviour(gen_server).
    │ │ │ │  
    │ │ │ │ --export([start_link/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ │ +-export([start_link/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ │  
    │ │ │ │ -start_link() ->
    │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ │ +start_link() ->
    │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ │  
    │ │ │ │ -alloc() ->
    │ │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ │ +alloc() ->
    │ │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │ │  
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │ │  
    │ │ │ │ -init(_Args) ->
    │ │ │ │ -    {ok, channels()}.
    │ │ │ │ +init(_Args) ->
    │ │ │ │ +    {ok, channels()}.
    │ │ │ │  
    │ │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -    {reply, Ch, Chs2}.
    │ │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +    {reply, Ch, Chs2}.
    │ │ │ │  
    │ │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ │ -    {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + Chs2 = free(Ch, Chs), │ │ │ │ + {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting a Gen_Server │ │ │ │

    │ │ │ │

    In the example in the previous section, gen_server is started by calling │ │ │ │ -ch3:start_link():

    start_link() ->
    │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ │ +ch3:start_link():

    start_link() ->
    │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ │ spawns and links to a new process, a gen_server.

    • The first argument, {local, ch3}, specifies the name. │ │ │ │ The gen_server is then locally registered as ch3.

      If the name is omitted, the gen_server is not registered. Instead its pid │ │ │ │ must be used. The name can also be given as {global, Name}, in which case │ │ │ │ the gen_server is registered using global:register_name/2.

    • The second argument, ch3, is the name of the callback module, which is │ │ │ │ the module where the callback functions are located.

      The interface functions (start_link/0, alloc/0, and free/1) are located │ │ │ │ in the same module as the callback functions (init/1, handle_call/3, and │ │ │ │ handle_cast/2). It is usually good programming practice to have the code │ │ │ │ corresponding to one process contained in a single module.

    • The third argument, [], is a term that is passed as is to the callback │ │ │ │ function init. Here, init does not need any indata and ignores the │ │ │ │ argument.

    • The fourth argument, [], is a list of options. See gen_server │ │ │ │ for the available options.

    If name registration succeeds, the new gen_server process calls the callback │ │ │ │ function ch3:init([]). init is expected to return {ok, State}, where │ │ │ │ State is the internal state of the gen_server. In this case, the state is │ │ │ │ -the available channels.

    init(_Args) ->
    │ │ │ │ -    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ │ +the available channels.

    init(_Args) ->
    │ │ │ │ +    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ │ gen_server has been initialized and is ready to receive requests.

    gen_server:start_link/4 must be used if the gen_server is part of │ │ │ │ a supervision tree, meaning that it was started by a supervisor. There │ │ │ │ is another function, gen_server:start/4, to start a standalone │ │ │ │ gen_server that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -126,32 +126,32 @@ │ │ │ │

    │ │ │ │

    The synchronous request alloc() is implemented using gen_server:call/2:

    alloc() ->
    │ │ │ │      gen_server:call(ch3, alloc).

    ch3 is the name of the gen_server and must agree with the name │ │ │ │ used to start it. alloc is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ When the request is received, the gen_server calls │ │ │ │ handle_call(Request, From, State), which is expected to return │ │ │ │ a tuple {reply,Reply,State1}. Reply is the reply that is to be sent back │ │ │ │ -to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ │ +to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ │ set of remaining available channels Chs2.

    Thus, the call ch3:alloc() returns the allocated channel Ch and the │ │ │ │ gen_server then waits for new requests, now with an updated list of │ │ │ │ available channels.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Asynchronous Requests - Cast │ │ │ │

    │ │ │ │ -

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ +

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ cast, and thus free, then returns ok.

    When the request is received, the gen_server calls │ │ │ │ handle_cast(Request, State), which is expected to return a tuple │ │ │ │ -{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ │ -    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ │ +{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ │ +    Chs2 = free(Ch, Chs),
    │ │ │ │ +    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ │ The gen_server is now ready for new requests.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │ │

    │ │ │ │

    │ │ │ │ @@ -162,65 +162,65 @@ │ │ │ │

    │ │ │ │

    If the gen_server is part of a supervision tree, no stop function is needed. │ │ │ │ The gen_server is automatically terminated by its supervisor. Exactly how │ │ │ │ this is done is defined by a shutdown strategy │ │ │ │ set in the supervisor.

    If it is necessary to clean up before termination, the shutdown strategy │ │ │ │ must be a time-out value and the gen_server must be set to trap exit signals │ │ │ │ in function init. When ordered to shutdown, the gen_server then calls │ │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │      ...,
    │ │ │ │ -    process_flag(trap_exit, true),
    │ │ │ │ +    process_flag(trap_exit, true),
    │ │ │ │      ...,
    │ │ │ │ -    {ok, State}.
    │ │ │ │ +    {ok, State}.
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(shutdown, State) ->
    │ │ │ │ +terminate(shutdown, State) ->
    │ │ │ │      %% Code for cleaning up here
    │ │ │ │      ...
    │ │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │ │

    │ │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ │ can be useful, for example:

    ...
    │ │ │ │ -export([stop/0]).
    │ │ │ │ +export([stop/0]).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ │ +stop() ->
    │ │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -handle_cast(stop, State) ->
    │ │ │ │ -    {stop, normal, State};
    │ │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ │ +handle_cast(stop, State) ->
    │ │ │ │ +    {stop, normal, State};
    │ │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │ │      ...
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(normal, State) ->
    │ │ │ │ +terminate(normal, State) ->
    │ │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ │ a normal termination and State1 is a new value for the state │ │ │ │ of the gen_server. This causes the gen_server to call │ │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │ │

    │ │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │      %% Code to handle exits here.
    │ │ │ │      ...
    │ │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │      %% Code to convert state (and more) during code change.
    │ │ │ │      ...
    │ │ │ │ -    {ok, NewState}.
    │ │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/funs.xhtml │ │ │ │ @@ -22,399 +22,399 @@ │ │ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

    │ │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ │ to write several different functions that have a similar structure, but differ │ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

    │ │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ │ -    print_list(Stream, T);
    │ │ │ │ -print_list(Stream, []) ->
    │ │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ │ +    print_list(Stream, T);
    │ │ │ │ +print_list(Stream, []) ->
    │ │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │      Pid ! Msg,
    │ │ │ │ -    broadcast(Msg, Pids);
    │ │ │ │ -broadcast(_, []) ->
    │ │ │ │ +    broadcast(Msg, Pids);
    │ │ │ │ +broadcast(_, []) ->
    │ │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ -    F(H),
    │ │ │ │ -    foreach(F, T);
    │ │ │ │ -foreach(F, []) ->
    │ │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ +    F(H),
    │ │ │ │ +    foreach(F, T);
    │ │ │ │ +foreach(F, []) ->
    │ │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │ │

    │ │ │ │

    Funs are written with the following syntax (see │ │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │          ...
    │ │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ --export([t1/0, t2/0]).
    │ │ │ │ --import(lists, [map/2]).
    │ │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ +-export([t1/0, t2/0]).
    │ │ │ │ +-import(lists, [map/2]).
    │ │ │ │  
    │ │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ -   apply(F, Args);
    │ │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ +   apply(F, Args);
    │ │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ │ representation of a fun. The check_process_code/2 │ │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │ │

    │ │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.

The following examples illustrate these rules:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
│ │ │ │ +    file:close(Stream).

Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ │ file:open line.

As any variable that occurs in the head of a fun is considered a new variable, │ │ │ │ -it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ -    {ok, Stream} = file:open(File, write),
│ │ │ │ -    foreach(fun(File) ->
│ │ │ │ -                io:format(Stream,"~p~n",[File])
│ │ │ │ -            end, List),
│ │ │ │ -    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ +it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(File) ->
│ │ │ │ +                io:format(Stream,"~p~n",[File])
│ │ │ │ +            end, List),
│ │ │ │ +    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ │ diagnostic:

./FileName.erl:Line: Warning: variable 'File'
│ │ │ │        shadowed in 'fun'

This indicates that the variable File, which is defined inside the fun, │ │ │ │ collides with the variable File, which is defined outside the fun.

The rules for importing variables into a fun has the consequence that certain │ │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ │ -argument is Y:

f(...) ->
│ │ │ │ +argument is Y:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(X) when X == Y ->
│ │ │ │ +    map(fun(X) when X == Y ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ -    ...

instead of writing the following code:

f(...) ->
│ │ │ │ +        end, ...)
│ │ │ │ +    ...

instead of writing the following code:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(Y) ->
│ │ │ │ +    map(fun(Y) ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ +        end, ...)
│ │ │ │      ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │ │

│ │ │ │

The following examples show a dialogue with the Erlang shell. All the higher │ │ │ │ order functions discussed are exported from the module lists.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

│ │ │ │ -

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ -map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ +

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ +map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ list.

When a new fun is defined in the shell, the value of the fun is printed as │ │ │ │ -Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │ +Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:map(Double, [1,2,3,4,5]).
│ │ │ │ -[2,4,6,8,10]

│ │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ │ +[2,4,6,8,10]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │ │

│ │ │ │ -

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ +

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │          true  ->  true;
│ │ │ │ -        false ->  any(Pred, T)
│ │ │ │ +        false ->  any(Pred, T)
│ │ │ │      end;
│ │ │ │ -any(Pred, []) ->
│ │ │ │ +any(Pred, []) ->
│ │ │ │      false.

A predicate is a function that returns true or false. any is true if │ │ │ │ there is a term X in the list such that P(X) is true.

A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ │ -10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │ +10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:any(Big, [1,2,3,4]).
│ │ │ │ +> lists:any(Big, [1,2,3,4]).
│ │ │ │  false
│ │ │ │ -> lists:any(Big, [1,2,3,12,5]).
│ │ │ │ +> lists:any(Big, [1,2,3,12,5]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │ │

│ │ │ │ -

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  ->  all(Pred, T);
│ │ │ │ +

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  ->  all(Pred, T);
│ │ │ │          false ->  false
│ │ │ │      end;
│ │ │ │ -all(Pred, []) ->
│ │ │ │ -    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │ +all(Pred, []) ->
│ │ │ │ +    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │  false
│ │ │ │ -> lists:all(Big, [12,13,14,15]).
│ │ │ │ +> lists:all(Big, [12,13,14,15]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

│ │ │ │ -

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ -    F(H),
│ │ │ │ -    foreach(F, T);
│ │ │ │ -foreach(F, []) ->
│ │ │ │ +

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ +    F(H),
│ │ │ │ +    foreach(F, T);
│ │ │ │ +foreach(F, []) ->
│ │ │ │      ok.

The function is applied to each argument in the list. foreach returns ok. It │ │ │ │ -is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │ +is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │  1
│ │ │ │  2
│ │ │ │  3
│ │ │ │  4
│ │ │ │  ok

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │ │

│ │ │ │ -

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ -    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ -foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ +

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ +    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ +foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ │ return a new accumulator, which is used the next time the function is called.

If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ │ -lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ -["I","like","Erlang"]
│ │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ -11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │ +lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ +["I","like","Erlang"]
│ │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ +11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │  Sum = 0,
│ │ │ │ -while( L != []){
│ │ │ │ -    Sum += length(head(L)),
│ │ │ │ -    L = tail(L)
│ │ │ │ +while( L != []){
│ │ │ │ +    Sum += length(head(L)),
│ │ │ │ +    L = tail(L)
│ │ │ │  end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │ │

│ │ │ │ -

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ -    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ -    {[R|Rs], Accu2};
│ │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ -then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ -(X) -> X
│ │ │ │ +

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ +    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ +    {[R|Rs], Accu2};
│ │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ +then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ +(X) -> X
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │  > Upcase_word =
│ │ │ │ -fun(X) ->
│ │ │ │ -lists:map(Upcase, X)
│ │ │ │ +fun(X) ->
│ │ │ │ +lists:map(Upcase, X)
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Upcase_word("Erlang").
│ │ │ │ +> Upcase_word("Erlang").
│ │ │ │  "ERLANG"
│ │ │ │ -> lists:map(Upcase_word, L).
│ │ │ │ -["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ -{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ -end, 0, L).
│ │ │ │ -{["I","LIKE","ERLANG"],11}

│ │ │ │ +> lists:map(Upcase_word, L). │ │ │ │ +["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ +{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ +end, 0, L).
│ │ │ │ +{["I","LIKE","ERLANG"],11}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │ │

│ │ │ │

lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ │ -in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ -    case F(H) of
│ │ │ │ -        true  -> [H|filter(F, T)];
│ │ │ │ -        false -> filter(F, T)
│ │ │ │ +in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ +    case F(H) of
│ │ │ │ +        true  -> [H|filter(F, T)];
│ │ │ │ +        false -> filter(F, T)
│ │ │ │      end;
│ │ │ │ -filter(F, []) -> [].
> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ -[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ +filter(F, []) -> [].

> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ +[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ │ -the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ +the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │ │

│ │ │ │

lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ │ -P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> [H|takewhile(Pred, T)];
│ │ │ │ -        false -> []
│ │ │ │ +P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> [H|takewhile(Pred, T)];
│ │ │ │ +        false -> []
│ │ │ │      end;
│ │ │ │ -takewhile(Pred, []) ->
│ │ │ │ -    [].
> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[200,500,45]

│ │ │ │ +takewhile(Pred, []) -> │ │ │ │ + [].

> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[200,500,45]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │ │

│ │ │ │ -

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> dropwhile(Pred, T);
│ │ │ │ -        false -> [H|T]
│ │ │ │ +

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> dropwhile(Pred, T);
│ │ │ │ +        false -> [H|T]
│ │ │ │      end;
│ │ │ │ -dropwhile(Pred, []) ->
│ │ │ │ -    [].
> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[5,3,45,6]

│ │ │ │ +dropwhile(Pred, []) -> │ │ │ │ + [].

> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[5,3,45,6]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │ │

│ │ │ │

lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ -    splitwith(Pred, L, []).
│ │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ +    splitwith(Pred, L, []).
│ │ │ │  
│ │ │ │ -splitwith(Pred, [H|T], L) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ -        false -> {reverse(L), [H|T]}
│ │ │ │ +splitwith(Pred, [H|T], L) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ +        false -> {reverse(L), [H|T]}
│ │ │ │      end;
│ │ │ │ -splitwith(Pred, [], L) ->
│ │ │ │ -    {reverse(L), []}.
> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -{[200,500,45],[5,3,45,6]}

│ │ │ │ +splitwith(Pred, [], L) -> │ │ │ │ + {reverse(L), []}.

> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +{[200,500,45],[5,3,45,6]}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │ │

│ │ │ │

So far, only functions that take funs as arguments have been described. More │ │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ │ following examples illustrate these type of functions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │ │

│ │ │ │

Adder(X) is a function that given X, returns a new function G such that │ │ │ │ -G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │ +G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6 = Adder(6).
│ │ │ │ +> Add6 = Adder(6).
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6(10).
│ │ │ │ +> Add6(10).
│ │ │ │  16

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │ │

│ │ │ │ -

The idea is to write something like:

-module(lazy).
│ │ │ │ --export([ints_from/1]).
│ │ │ │ -ints_from(N) ->
│ │ │ │ -    fun() ->
│ │ │ │ -            [N|ints_from(N+1)]
│ │ │ │ -    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │ +

The idea is to write something like:

-module(lazy).
│ │ │ │ +-export([ints_from/1]).
│ │ │ │ +ints_from(N) ->
│ │ │ │ +    fun() ->
│ │ │ │ +            [N|ints_from(N+1)]
│ │ │ │ +    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> XX().
│ │ │ │ -[1|#Fun<lazy.0.29874839>]
│ │ │ │ -> hd(XX()).
│ │ │ │ +> XX().
│ │ │ │ +[1|#Fun<lazy.0.29874839>]
│ │ │ │ +> hd(XX()).
│ │ │ │  1
│ │ │ │ -> Y = tl(XX()).
│ │ │ │ +> Y = tl(XX()).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> hd(Y()).
│ │ │ │ +> hd(Y()).
│ │ │ │  2

And so on. This is an example of "lazy embedding".

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │ │

│ │ │ │ -

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ +

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ {ok, Tree, Toks1}.

  • Tree is a parse tree.
  • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ │ structure that was correctly parsed.

An unsuccessful parse returns fail.

The following example illustrates a simple, functional parser that parses the │ │ │ │ grammar:

(a | b) & (c | d)

The following code defines a function pconst(X) in the module funparse, │ │ │ │ -which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ +which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ +    fun (T) ->
│ │ │ │         case T of
│ │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │             _      -> fail
│ │ │ │         end
│ │ │ │ -    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │ +    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │  #Fun<funparse.0.22674075>
│ │ │ │ -> P1([a,b,c]).
│ │ │ │ -{ok,{const,a},[b,c]}
│ │ │ │ -> P1([x,y,z]).
│ │ │ │ +> P1([a,b,c]).
│ │ │ │ +{ok,{const,a},[b,c]}
│ │ │ │ +> P1([x,y,z]).
│ │ │ │  fail

Next, the two higher order functions pand and por are defined. They combine │ │ │ │ -primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R1, T1} ->
│ │ │ │ -                case P2(T1) of
│ │ │ │ -                    {ok, R2, T2} ->
│ │ │ │ -                        {ok, {'and', R1, R2}};
│ │ │ │ +primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R1, T1} ->
│ │ │ │ +                case P2(T1) of
│ │ │ │ +                    {ok, R2, T2} ->
│ │ │ │ +                        {ok, {'and', R1, R2}};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end;
│ │ │ │              fail ->
│ │ │ │                  fail
│ │ │ │          end
│ │ │ │      end.

Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ │ -G2:

por(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R, T1} ->
│ │ │ │ -                {ok, {'or',1,R}, T1};
│ │ │ │ +G2:

por(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R, T1} ->
│ │ │ │ +                {ok, {'or',1,R}, T1};
│ │ │ │              fail ->
│ │ │ │ -                case P2(T) of
│ │ │ │ -                    {ok, R1, T1} ->
│ │ │ │ -                        {ok, {'or',2,R1}, T1};
│ │ │ │ +                case P2(T) of
│ │ │ │ +                    {ok, R1, T1} ->
│ │ │ │ +                        {ok, {'or',2,R1}, T1};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end
│ │ │ │          end
│ │ │ │      end.

The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ │ -code addresses this problem:

grammar() ->
│ │ │ │ -    pand(
│ │ │ │ -         por(pconst(a), pconst(b)),
│ │ │ │ -         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ -    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([a,d]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([b,c]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([b,d]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([a,b]).
│ │ │ │ +code addresses this problem:

grammar() ->
│ │ │ │ +    pand(
│ │ │ │ +         por(pconst(a), pconst(b)),
│ │ │ │ +         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ +    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([a,d]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([b,c]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([b,d]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([a,b]).
│ │ │ │  fail
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/expressions.xhtml │ │ │ │ @@ -56,81 +56,81 @@ │ │ │ │
Phone_number │ │ │ │ _ │ │ │ │ _Height │ │ │ │ name@node

Variables are bound to values using pattern matching. Erlang uses │ │ │ │ single assignment, that is, a variable can only be bound once.

The anonymous variable is denoted by underscore (_) and can be used when a │ │ │ │ variable is required but its value can be ignored.

Example:

[H|_] = [1,2,3]

Variables starting with underscore (_), for example, _Height, are normal │ │ │ │ variables, not anonymous. However, they are ignored by the compiler in the sense │ │ │ │ -that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ -    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ -    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ -the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ -    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ -following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ +that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ +    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ +    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ +the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ +    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ +following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ an if, case, or receive expression must be bound in all branches to have a │ │ │ │ value outside the expression. Otherwise they are regarded as unsafe outside │ │ │ │ the expression.

For the try expression variable scoping is limited so that variables bound in │ │ │ │ the expression are always unsafe outside the expression.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Patterns │ │ │ │

│ │ │ │

A pattern has the same structure as a term but can contain unbound variables.

Example:

Name1
│ │ │ │ -[H|T]
│ │ │ │ -{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ +[H|T] │ │ │ │ +{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ receive expressions, and │ │ │ │ match expressions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Compound Pattern Operator │ │ │ │

│ │ │ │

If Pattern1 and Pattern2 are valid patterns, the following is also a valid │ │ │ │ pattern:

Pattern1 = Pattern2

When matched against a term, both Pattern1 and Pattern2 are matched against │ │ │ │ -the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ -    Signal = {connect,From,To,Number,Options},
│ │ │ │ +the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ +    Signal = {connect,From,To,Number,Options},
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ -    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │ +    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │      ignore.

The compound pattern operator does not imply that its operands are matched in │ │ │ │ any particular order. That means that it is not legal to bind a variable in │ │ │ │ Pattern1 and use it in Pattern2, or vice versa.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String Prefix in Patterns │ │ │ │

│ │ │ │ -

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ +

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Expressions in Patterns │ │ │ │

│ │ │ │

An arithmetic expression can be used within a pattern if it meets both of the │ │ │ │ -following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ -    {?THRESHOLD+1, ok} -> ...

│ │ │ │ +following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ +    {?THRESHOLD+1, ok} -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Match Operator │ │ │ │

│ │ │ │

The following matches Pattern against Expr:

Pattern = Expr

If the matching succeeds, any unbound variable in the pattern becomes bound and │ │ │ │ the value of Expr is returned.

If multiple match operators are applied in sequence, they will be evaluated from │ │ │ │ -right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ -{answer,42}
│ │ │ │ +right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ +{answer,42}
│ │ │ │  2> A.
│ │ │ │  answer
│ │ │ │  3> B.
│ │ │ │  42
│ │ │ │  4> T.
│ │ │ │ -{answer,42}
│ │ │ │ -5> {C, D} = [1, 2].
│ │ │ │ +{answer,42}
│ │ │ │ +5> {C, D} = [1, 2].
│ │ │ │  ** exception error: no match of right-hand side value [1,2]

Because multiple match operators are evaluated from right to left, it means │ │ │ │ that:

Pattern1 = Pattern2 = . . . = PatternN = Expression

is equivalent to:

Temporary = Expression,
│ │ │ │  PatternN = Temporary,
│ │ │ │     .
│ │ │ │     .
│ │ │ │     .,
│ │ │ │  Pattern2 = Temporary,
│ │ │ │ @@ -144,30 +144,30 @@
│ │ │ │  can safely be skipped on a first reading.

The = character is used to denote two similar but distinct operators: the │ │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ │ determined by context.

The compound pattern operator is used to construct a compound pattern from two │ │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ │ -same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │ +same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │  * 1:7: variable 'Key' is unbound
│ │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
│ │ │ │ -{{1,2},3}
│ │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
│ │ │ │ -{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ │ +{{1,2},3} │ │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ │ +{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ │ -are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ -#{key => key2,key2 => value}
│ │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │ +are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ +#{key => key2,key2 => value}
│ │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │  value
│ │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │  value
│ │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │  * 1:12: variable 'Key' is unbound
│ │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │  42

The expression at prompt 2> first matches the value of variable M against │ │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ │ variable Value.

The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ │ @@ -181,30 +181,30 @@ │ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │ │

│ │ │ │ -
ExprF(Expr1,...,ExprN)
│ │ │ │ -ExprM:ExprF(Expr1,...,ExprN)

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ +

ExprF(Expr1,...,ExprN)
│ │ │ │ +ExprM:ExprF(Expr1,...,ExprN)

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ │ -This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ +This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ an atom or evaluate to a fun.

If ExprF is an atom, the function is said to be called by using the │ │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ │ -imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ -spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ -Fun1(3).
│ │ │ │ +imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ +spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ +Fun1(3).
│ │ │ │  4
│ │ │ │ -2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ -[1,2,3,4]
│ │ │ │ +2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ +[1,2,3,4]
│ │ │ │  3>

Notice that when calling a local function, there is a difference between using │ │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ │ latest version of the module. See │ │ │ │ Compilation and Code Loading and │ │ │ │ Function Evaluation.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -221,40 +221,40 @@ │ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ │ auto-imported BIFs do not silently change the behavior of old code.

However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ │ -you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │ +you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ -length([]) ->
│ │ │ │ +length([]) ->
│ │ │ │      0;
│ │ │ │ -length([H|T]) ->
│ │ │ │ -    1 + length(T). %% Calls the local function length/1
│ │ │ │ +length([H|T]) ->
│ │ │ │ +    1 + length(T). %% Calls the local function length/1
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │                                    %% which is allowed in guards
│ │ │ │      long.

The same logic applies to explicitly imported functions from other modules, as │ │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ │ -another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │ +another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ --import(mod,[length/1]).
│ │ │ │ +-import(mod,[length/1]).
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │                                     %% which is allowed in guards
│ │ │ │  
│ │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │  
│ │ │ │ -f(X) ->
│ │ │ │ -    length(X).                     %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ +f(X) -> │ │ │ │ + length(X). %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ │ qualified function name.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -266,40 +266,40 @@ │ │ │ │ ...; │ │ │ │ GuardSeqN -> │ │ │ │ BodyN │ │ │ │ end

The branches of an if-expression are scanned sequentially until a guard │ │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

The return value of Body is the return value of the if expression.

If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ │ -that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │ +that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │      if
│ │ │ │          X > Y ->
│ │ │ │              true;
│ │ │ │          true -> % works as an 'else' branch
│ │ │ │              false
│ │ │ │      end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │ │

│ │ │ │
case Expr of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ │ GuardSeq is true, the corresponding Body is evaluated.

The return value of Body is the return value of the case expression.

If there is no matching pattern with a true guard sequence, a case_clause │ │ │ │ -run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │ +run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │      case Signal of
│ │ │ │ -        {signal, _What, _From, _To} ->
│ │ │ │ +        {signal, _What, _From, _To} ->
│ │ │ │              true;
│ │ │ │ -        {signal, _What, _To} ->
│ │ │ │ +        {signal, _What, _To} ->
│ │ │ │              true;
│ │ │ │          _Else ->
│ │ │ │              false
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -317,57 +317,57 @@ │ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ │ block is Expr2.

None of the variables bound in a maybe block must be used in the code that │ │ │ │ follows the block.

Here is an example:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  end

Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

Now let us assume that a() returns error. The conditional match operator in │ │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ │ b() returns wrong, the return value of the maybe block is wrong.

Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ │ -expression fails to match the pattern.

The example can be written in a less succient way using nested case expressions:

case a() of
│ │ │ │ -    {ok, A} ->
│ │ │ │ +expression fails to match the pattern.

The example can be written in a less succient way using nested case expressions:

case a() of
│ │ │ │ +    {ok, A} ->
│ │ │ │          true = A >= 0,
│ │ │ │ -        case b() of
│ │ │ │ -            {ok, B} ->
│ │ │ │ +        case b() of
│ │ │ │ +            {ok, B} ->
│ │ │ │                  A + B;
│ │ │ │              Other1 ->
│ │ │ │                  Other1
│ │ │ │          end;
│ │ │ │      Other2 ->
│ │ │ │          Other2
│ │ │ │  end

The maybe block can be augmented with else clauses:

maybe
│ │ │ │      Expr1,
│ │ │ │      ...,
│ │ │ │      ExprN
│ │ │ │  else
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

If a conditional match operator fails, the failed expression is matched against │ │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ │ maybe block.

If there is no matching pattern with a true guard sequence, an else_clause │ │ │ │ run-time error occurs.

None of the variables bound in a maybe block must be used in the else │ │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ │ code that follows the maybe block.

Here is the previous example augmented with else clauses:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  else
│ │ │ │      error -> error;
│ │ │ │      wrong -> error
│ │ │ │  end

The else clauses translate the failing value from the conditional match │ │ │ │ operators to the value error. If the failing value is not one of the │ │ │ │ recognized values, a else_clause run-time error occurs.

│ │ │ │ @@ -386,75 +386,75 @@ │ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │ │

│ │ │ │
receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

Fetches a received message present in the message queue of the process. The │ │ │ │ first message in the message queue is matched sequentially against the patterns │ │ │ │ from top to bottom. If no match was found, the matching sequence is repeated for │ │ │ │ the second message in the queue, and so on. Messages are queued in the │ │ │ │ order they were received. If a match │ │ │ │ succeeds, that is, if the Pattern matches and the optional guard sequence │ │ │ │ GuardSeq is true, then the message is removed from the message queue and the │ │ │ │ corresponding Body is evaluated. All other messages in the message queue │ │ │ │ remain unchanged.

The return value of Body is the return value of the receive expression.

receive never fails. The execution is suspended, possibly indefinitely, until │ │ │ │ a message arrives that matches one of the patterns and with a true guard │ │ │ │ -sequence.

Example:

wait_for_onhook() ->
│ │ │ │ +sequence.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      end.

The receive expression can be augmented with a timeout:

receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

receive...after works exactly as receive, except that if no matching message │ │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ │ expression. ExprT is to evaluate to an integer, or the atom infinity. The │ │ │ │ allowed integer range is from 0 to 4294967295, that is, the longest possible │ │ │ │ timeout is almost 50 days. With a zero value the timeout occurs immediately if │ │ │ │ there is no matching message in the message queue.

The atom infinity will make the process wait indefinitely for a matching │ │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ │ -values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │ +values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      after
│ │ │ │          60000 ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            error()
│ │ │ │ +            disconnect(),
│ │ │ │ +            error()
│ │ │ │      end.

It is legal to use a receive...after expression with no branches:

receive
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

This construction does not consume any messages, only suspends execution in the │ │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ -    spawn(m, timer, [self()]).
│ │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ +    spawn(m, timer, [self()]).
│ │ │ │  
│ │ │ │ -timer(Pid) ->
│ │ │ │ +timer(Pid) ->
│ │ │ │      receive
│ │ │ │      after
│ │ │ │          5000 ->
│ │ │ │              Pid ! timeout
│ │ │ │      end.

For more information on timers in Erlang in general, see the │ │ │ │ Timers section of the │ │ │ │ Time and Time Correction in Erlang │ │ │ │ @@ -496,21 +496,21 @@ │ │ │ │ false │ │ │ │ 4> 0.0 =:= -0.0. │ │ │ │ false │ │ │ │ 5> 0.0 =:= +0.0. │ │ │ │ true │ │ │ │ 6> 1 > a. │ │ │ │ false │ │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ false │ │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ true │ │ │ │ -9> <<2:2>> < <<128>>. │ │ │ │ +9> <<2:2>> < <<128>>. │ │ │ │ true │ │ │ │ -10> <<3:2>> < <<128>>. │ │ │ │ +10> <<3:2>> < <<128>>. │ │ │ │ false

Note

Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ │ and -0.0 to be the same term.

This was changed in OTP 27 but legacy code may have expected them to be │ │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ │ in a term equivalence test.

If you need to match 0.0 specifically, the warning can be silenced by │ │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ │ interpret the match as being done on purpose.

│ │ │ │ @@ -536,15 +536,15 @@ │ │ │ │ 0 │ │ │ │ 8> 2#10 bor 2#01. │ │ │ │ 3 │ │ │ │ 9> a + 10. │ │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ │ in operator +/2 │ │ │ │ called as a + 10 │ │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ │ ** exception error: a system limit has been reached │ │ │ │ in operator bsl/2 │ │ │ │ called as 1 bsl 18446744073709551616

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ │ @@ -563,136 +563,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │ │

│ │ │ │
Expr1 orelse Expr2
│ │ │ │  Expr1 andalso Expr2

Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

  • Expr1 evaluates to false in an orelse expression.

or

  • Expr1 evaluates to true in an andalso expression.

Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ │ -Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ -never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ -         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ -andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ -all(_, []) ->
│ │ │ │ +Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ +never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ +         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ +andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ +all(_, []) ->
│ │ │ │      true.

Change

Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ │ and as consequence, andalso and orelse were not tail-recursive.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │ │

│ │ │ │
Expr1 ++ Expr2
│ │ │ │  Expr1 -- Expr2

The list concatenation operator ++ appends its second argument to its first │ │ │ │ and returns the resulting list.

The list subtraction operator -- produces a list that is a copy of the first │ │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ │ -the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ -[1,2,3,4,5]
│ │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ -[3,1,2]

│ │ │ │ +the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ +[1,2,3,4,5]
│ │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ +[3,1,2]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │ │

│ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │ │

│ │ │ │

Constructing a new map is done by letting an expression K be associated with │ │ │ │ -another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ -association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ +another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ +association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ then the resulting terms are used as key and value respectively.

Keys and values are separated by the => arrow and associations are separated │ │ │ │ -by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ -M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ -map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ -#{1 => b }
│ │ │ │ -2> #{1.0 => a, 1 => b}.
│ │ │ │ -#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ +by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ +M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ +map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ +#{1 => b }
│ │ │ │ +2> #{1.0 => a, 1 => b}.
│ │ │ │ +#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ │ two matching keys.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │ │

│ │ │ │

Updating a map has a similar syntax as constructing it.

An expression defining the map to be updated is put in front of the expression │ │ │ │ -defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ +defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ created from key K to value V.

If key K matches an existing key in map M, its associated value is replaced │ │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ │ -map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ +map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ that evaluates to an existing key in M.

If key K does not match any existing keys in map M, an exception of type │ │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ │ -expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ -M1 = M0#{a => 0},
│ │ │ │ -M2 = M1#{a => 1, b => 2},
│ │ │ │ -M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ -#{1 => a }
│ │ │ │ -2> M#{1.0 => b}.
│ │ │ │ -#{1 => a, 1.0 => b}.
│ │ │ │ -3> M#{1 := b}.
│ │ │ │ -#{1 => b}
│ │ │ │ -4> M#{1.0 := b}.
│ │ │ │ +expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ +M1 = M0#{a => 0},
│ │ │ │ +M2 = M1#{a => 1, b => 2},
│ │ │ │ +M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ +#{1 => a }
│ │ │ │ +2> M#{1.0 => b}.
│ │ │ │ +#{1 => a, 1.0 => b}.
│ │ │ │ +3> M#{1 := b}.
│ │ │ │ +#{1 => b}
│ │ │ │ +4> M#{1.0 := b}.
│ │ │ │  ** exception error: bad argument

As in construction, the order in which the key and value expressions are │ │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ │ case, the latter value is used.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │ │

│ │ │ │ -

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ +

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ guard expression, with all variables already │ │ │ │ bound. V can be any pattern with either bound or unbound variables.

If the variable V is unbound, it becomes bound to the value associated with │ │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ │ must match the value associated with K in M.

Change

Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ │ -either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ -2> #{"tuple" := {1,B}} = M.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ +either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │ +2> #{"tuple" := {1,B}} = M.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │  3> B.
│ │ │ │ -2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ +2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ │ associated values of their respective keys.

If the matching conditions are not met the match fails.

Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ │ as a delimiter for the associations.

The order in which keys are declared in matching has no relevance.

Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ │ -keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ -fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │ +keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ +fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := start}};
│ │ │ │ +    {reply, ok, S#{state := start}};
│ │ │ │  
│ │ │ │  %% only change if started
│ │ │ │ -handle_call(change, From, #{state := start} = S) ->
│ │ │ │ +handle_call(change, From, #{state := start} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := changed}};

│ │ │ │ + {reply, ok, S#{state := changed}};

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │ │

│ │ │ │

Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ │ expressions.

The following guard BIFs handle maps:

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │ │

│ │ │ │

The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ │ -ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ -<<E1>>
│ │ │ │ -<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ +ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ +<<E1>>
│ │ │ │ +<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ │ of the bit string.

Each segment specification Ei is a value, whose default type is integer, │ │ │ │ followed by an optional size expression and an optional type specifier list.

Ei = Value |
│ │ │ │       Value:Size |
│ │ │ │       Value/TypeSpecifierList |
│ │ │ │       Value:Size/TypeSpecifierList

When used in a bit string construction, Value is an expression that is to │ │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ │ @@ -703,34 +703,34 @@ │ │ │ │ guard expression that evaluates to an │ │ │ │ integer. All variables in the guard expression must be already bound.

Change

Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ │ bound to an integer.

The value of Size specifies the size of the segment in units (see below). The │ │ │ │ default value depends on the type (see below):

  • For integer it is 8.
  • For float it is 64.
  • For binary and bitstring it is the whole binary or bit string.

In matching, the default value for a binary or bit string segment is only valid │ │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ │ must have a size specification.

Binaries

A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ │ which is the most common and useful type of bit string.

A binary has a canonical representation in memory. Here follows a sequence of │ │ │ │ -bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ -information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ +information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
│ │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ -<<"abcde">>
│ │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +<<"abcde">>
│ │ │ │  3> A.
│ │ │ │ -<<"abc">>
│ │ │ │ +<<"abc">>
│ │ │ │  4> B.
│ │ │ │ -<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ +<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ of the segment is implicitly determined by the type and value itself.

TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

  • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ │ for more information about the utf types.

  • Signedness= signed | unsigned - Only matters for matching and when │ │ │ │ the type is integer. The default is unsigned.

  • Endianness= big | little | native - Specifies byte level (octet │ │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ │ -default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ +default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ │ for the types utf8, utf16, and utf32.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -755,41 +755,41 @@ │ │ │ │ │ │ │ │ Binary segments │ │ │ │

│ │ │ │

In this section, the phrase "binary segment" refers to any one of the segment │ │ │ │ types binary, bitstring, bytes, and bits.

See also the paragraphs about Binaries.

When constructing binaries and no size is specified for a binary segment, the │ │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ │ -the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -3> <<(<<"abc">>)/binary>>.
│ │ │ │ -<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ +the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +3> <<(<<"abc">>)/binary>>.
│ │ │ │ +<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ segment has a unit value of 8.

Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ │ -8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ -** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ -<<1:1>>
│ │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ -<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ +8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ +** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ +<<1:1>>
│ │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ +<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ │ -divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ -<<>>
│ │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │ +divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ +<<>>
│ │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │  ** exception error: no match of right hand side value <<"a">>
│ │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ -<<"ab">>
│ │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ +<<"ab">>
│ │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │  ** exception error: no match of right hand side value <<"abc">>
│ │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
│ │ │ │ -<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ │ +<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

When constructing binaries, the size of the binary being interpolated into the │ │ │ │ -constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ -<<"ab">>
│ │ │ │ -2> <<(<<"a">>):2/binary>>.
│ │ │ │ +constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ +<<"ab">>
│ │ │ │ +2> <<(<<"a">>):2/binary>>.
│ │ │ │  ** exception error: construction of binary failed
│ │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │ │

│ │ │ │ @@ -805,78 +805,78 @@ │ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ │ returned value falls outside those ranges.

A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ │ Unicode standard.)

A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ │ -is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ -<<1,17,42>>
│ │ │ │ -2> Bin2 = <<"abc">>.
│ │ │ │ -<<97,98,99>>
│ │ │ │ +is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ +<<1,17,42>>
│ │ │ │ +2> Bin2 = <<"abc">>.
│ │ │ │ +<<97,98,99>>
│ │ │ │  
│ │ │ │ -3> Bin3 = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +3> Bin3 = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  5> C.
│ │ │ │  42
│ │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  7> D.
│ │ │ │  273
│ │ │ │  8> F.
│ │ │ │  42
│ │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  10> H.
│ │ │ │ -<<17,0,42>>
│ │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ -<<1,17,2,10:4>>
│ │ │ │ +<<17,0,42>>
│ │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ +<<1,17,2,10:4>>
│ │ │ │  12> J.
│ │ │ │ -<<17,2,10:4>>
│ │ │ │ +<<17,2,10:4>>
│ │ │ │  
│ │ │ │ -13> <<1024/utf8>>.
│ │ │ │ -<<208,128>>
│ │ │ │ +13> <<1024/utf8>>.
│ │ │ │ +<<208,128>>
│ │ │ │  
│ │ │ │ -14> <<1:1,0:7>>.
│ │ │ │ -<<128>>
│ │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
│ │ │ │ -<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ +14> <<1:1,0:7>>. │ │ │ │ +<<128>> │ │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ │ +<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │ │

│ │ │ │
fun
│ │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │                Body1;
│ │ │ │      ...;
│ │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │                BodyK
│ │ │ │  end

A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ │ Between them is to be a function declaration, similar to a │ │ │ │ regular function declaration, │ │ │ │ except that the function name is optional and is to be a variable, if any.

Variables in a fun head shadow the function name and both shadow variables in │ │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ │ -body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │ +body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -2> Fun1(2).
│ │ │ │ +2> Fun1(2).
│ │ │ │  3
│ │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -4> Fun2(7).
│ │ │ │ +4> Fun2(7).
│ │ │ │  gt
│ │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -6> Fun3(4).
│ │ │ │ +6> Fun3(4).
│ │ │ │  24

The following fun expressions are also allowed:

fun Name/Arity
│ │ │ │  fun Module:Name/Arity

In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ │ -specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ +specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ │ code for the module in which it is defined.

Change

Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ │ variables.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -886,35 +886,35 @@ │ │ │ │
catch Expr

Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ │ exception:

Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ │ recent function calls, see Exit Reasons.

Examples:

1> catch 1+2.
│ │ │ │  3
│ │ │ │  2> catch 1+a.
│ │ │ │ -{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │ +{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │  hello

If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ │ error occurs.

Change

Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ │ -it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │ +it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

Starting from Erlang/OTP 24, the parentheses can be omitted:

1> A = catch 42.
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │ │

│ │ │ │
try Exprs
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

This is an enhancement of catch. It gives the │ │ │ │ possibility to:

  • Distinguish between different exception classes.
  • Choose to handle only the desired ones.
  • Passing the others on to an enclosing try or catch, or to default error │ │ │ │ handling.

Notice that although the keyword catch is used in the try expression, there │ │ │ │ is not a catch expression within the try expression.

It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ │ @@ -924,47 +924,47 @@ │ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ │ matches.

If an exception occurs during evaluation of Exprs but there is no matching │ │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ │ expression.

If an exception occurs during evaluation of ExceptionBody, it is not caught.

It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ │ for throw:

try Exprs
│ │ │ │  catch
│ │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

The try expression can have an of section:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ │ case expression, except that if the matching fails, a │ │ │ │ try_clause run-time error occurs instead of a case_clause.

Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ │ not caught.

The try expression can also be augmented with an after section, intended to │ │ │ │ be used for cleanup with side effects:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  after
│ │ │ │      AfterBody
│ │ │ │  end

AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ │ expression is the same with an after section as without.

Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ │ @@ -987,40 +987,40 @@ │ │ │ │ ExpressionBody │ │ │ │ after │ │ │ │ AfterBody │ │ │ │ end │ │ │ │ │ │ │ │ try Exprs after AfterBody end

Next is an example of using after. This closes the file, even in the event of │ │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ │ -exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │ +exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │      try
│ │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ -        binary_to_term(Bin)
│ │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ +        binary_to_term(Bin)
│ │ │ │      after
│ │ │ │ -        file:close(F)
│ │ │ │ +        file:close(F)
│ │ │ │      end.

Next is an example of using try to emulate catch Expr:

try Expr
│ │ │ │  catch
│ │ │ │      throw:Term -> Term;
│ │ │ │ -    exit:Reason -> {'EXIT',Reason};
│ │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │ +    exit:Reason -> {'EXIT',Reason};
│ │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │  end

Variables bound in the various parts of these expressions have different scopes. │ │ │ │ Variables bound just after the try keyword are:

  • bound in the of section
  • unsafe in both the catch and after sections, as well as after the whole │ │ │ │ construct

Variables bound in of section are:

  • unbound in the catch section
  • unsafe in both the after section, as well as after the whole construct

Variables bound in the catch section are unsafe in the after section, as │ │ │ │ well as after the whole construct.

Variables bound in the after section are unsafe after the whole construct.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │ │

│ │ │ │ -
(Expr)

Parenthesized expressions are useful to override │ │ │ │ +

(Expr)

Parenthesized expressions are useful to override │ │ │ │ operator precedences, for example, in arithmetic │ │ │ │ expressions:

1> 1 + 2 * 3.
│ │ │ │  7
│ │ │ │ -2> (1 + 2) * 3.
│ │ │ │ +2> (1 + 2) * 3.
│ │ │ │  9

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │ │

│ │ │ │
begin
│ │ │ │ @@ -1032,71 +1032,71 @@
│ │ │ │    
│ │ │ │      
│ │ │ │    
│ │ │ │    Comprehensions
│ │ │ │  

│ │ │ │

Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ │ -depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ +depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ generator or a filter.

Bit string comprehensions construct bit strings or binaries. They have the │ │ │ │ -following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ +following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ │ -Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ +Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ is either a generator or a filter.

Change

Map comprehensions and map generators were introduced in Erlang/OTP 26.

There are three kinds of generators.

A list generator has the following syntax:

Pattern <- ListExpr

where ListExpr is an expression that evaluates to a list of terms.

A bit string generator has the following syntax:

BitstringPattern <= BitStringExpr

where BitStringExpr is an expression that evaluates to a bit string.

A map generator has the following syntax:

KeyPattern := ValuePattern <- MapExpression

where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

A filter is an expression that evaluates to true or false.

The variables in the generator patterns shadow previously bound variables, │ │ │ │ including variables bound in a previous generator pattern.

Variables bound in a generator expression are not visible outside the │ │ │ │ -expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │ +expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │  * 1:5: variable 'L' is unbound

A list comprehension returns a list, where the list elements are the result │ │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ │ filters are true.

A bit string comprehension returns a bit string, which is created by │ │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ │ bit string generator elements for which all filters are true.

A map comprehension returns a map, where the map elements are the result of │ │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ │ -occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

1> [X*2 || X <- [1,2,3]].
│ │ │ │ -[2,4,6]

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <= <<1,2,3>>].
│ │ │ │ -[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
│ │ │ │ -<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <- [1,2,3] >>.
│ │ │ │ -<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <- [1,2,3]}.
│ │ │ │ -#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
│ │ │ │ -#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ -[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ -[{a,b},{1,2}]

Combining elements from two list generators:

1> [{P,Q} || P <- [a,b,c], Q <- [1,2]].
│ │ │ │ -[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

More examples are provided in │ │ │ │ +occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

1> [X*2 || X <- [1,2,3]].
│ │ │ │ +[2,4,6]

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <= <<1,2,3>>].
│ │ │ │ +[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
│ │ │ │ +<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <- [1,2,3] >>.
│ │ │ │ +<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <- [1,2,3]}.
│ │ │ │ +#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
│ │ │ │ +#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ +[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ +[{a,b},{1,2}]

Combining elements from two list generators:

1> [{P,Q} || P <- [a,b,c], Q <- [1,2]].
│ │ │ │ +[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

More examples are provided in │ │ │ │ Programming Examples.

When there are no generators, a comprehension returns either a term constructed │ │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ -[2]
│ │ │ │ -2> [x || is_integer(x)].
│ │ │ │ -[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ +[2]
│ │ │ │ +2> [x || is_integer(x)].
│ │ │ │ +[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ depends on the expression:

  • If the expression is a guard expression, │ │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ │ evaluating to false.
  • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ │ -comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> [E || E <- List, E rem 2].
│ │ │ │ -[]
│ │ │ │ -3> [E || E <- List, E rem 2 =:= 0].
│ │ │ │ -[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │ +comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> [E || E <- List, E rem 2].
│ │ │ │ +[]
│ │ │ │ +3> [E || E <- List, E rem 2 =:= 0].
│ │ │ │ +[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -3> [E || E <- List, FaultyIsEven(E)].
│ │ │ │ +3> [E || E <- List, FaultyIsEven(E)].
│ │ │ │  ** exception error: bad filter 1
│ │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -5> [E || E <- List, IsEven(E)].
│ │ │ │ +5> [E || E <- List, IsEven(E)].
│ │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
│ │ │ │       in operator  rem/2
│ │ │ │          called as a rem 2
│ │ │ │ -6> [E || E <- List, is_integer(E), IsEven(E)].
│ │ │ │ -[2,4]

│ │ │ │ +6> [E || E <- List, is_integer(E), IsEven(E)]. │ │ │ │ +[2,4]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │ │

│ │ │ │

A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ │ ├── OEBPS/example.xhtml │ │ │ │ @@ -36,14 +36,14 @@ │ │ │ │ │ │ │ │ int bar(int y) { │ │ │ │ return y*2; │ │ │ │ }

The functions are deliberately kept as simple as possible, for readability │ │ │ │ reasons.

From an Erlang perspective, it is preferable to be able to call foo and bar │ │ │ │ without having to bother about that they are C functions:

% Erlang code
│ │ │ │  ...
│ │ │ │ -Res = complex:foo(X),
│ │ │ │ +Res = complex:foo(X),
│ │ │ │  ...

Here, the communication with C is hidden in the implementation of complex.erl. │ │ │ │ In the following sections, it is shown how this module can be implemented using │ │ │ │ the different interoperability mechanisms.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/events.xhtml │ │ │ │ @@ -40,43 +40,43 @@ │ │ │ │ event handler.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │

│ │ │ │

The callback module for the event handler writing error messages to the terminal │ │ │ │ -can look as follows:

-module(terminal_logger).
│ │ │ │ --behaviour(gen_event).
│ │ │ │ +can look as follows:

-module(terminal_logger).
│ │ │ │ +-behaviour(gen_event).
│ │ │ │  
│ │ │ │ --export([init/1, handle_event/2, terminate/2]).
│ │ │ │ +-export([init/1, handle_event/2, terminate/2]).
│ │ │ │  
│ │ │ │ -init(_Args) ->
│ │ │ │ -    {ok, []}.
│ │ │ │ +init(_Args) ->
│ │ │ │ +    {ok, []}.
│ │ │ │  
│ │ │ │ -handle_event(ErrorMsg, State) ->
│ │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, State}.
│ │ │ │ +handle_event(ErrorMsg, State) ->
│ │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, State}.
│ │ │ │  
│ │ │ │ -terminate(_Args, _State) ->
│ │ │ │ +terminate(_Args, _State) ->
│ │ │ │      ok.

The callback module for the event handler writing error messages to a file can │ │ │ │ -look as follows:

-module(file_logger).
│ │ │ │ --behaviour(gen_event).
│ │ │ │ +look as follows:

-module(file_logger).
│ │ │ │ +-behaviour(gen_event).
│ │ │ │  
│ │ │ │ --export([init/1, handle_event/2, terminate/2]).
│ │ │ │ +-export([init/1, handle_event/2, terminate/2]).
│ │ │ │  
│ │ │ │ -init(File) ->
│ │ │ │ -    {ok, Fd} = file:open(File, read),
│ │ │ │ -    {ok, Fd}.
│ │ │ │ -
│ │ │ │ -handle_event(ErrorMsg, Fd) ->
│ │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, Fd}.
│ │ │ │ +init(File) ->
│ │ │ │ +    {ok, Fd} = file:open(File, read),
│ │ │ │ +    {ok, Fd}.
│ │ │ │ +
│ │ │ │ +handle_event(ErrorMsg, Fd) ->
│ │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, Fd}.
│ │ │ │  
│ │ │ │ -terminate(_Args, Fd) ->
│ │ │ │ -    file:close(Fd).

The code is explained in the next sections.

│ │ │ │ +terminate(_Args, Fd) -> │ │ │ │ + file:close(Fd).

The code is explained in the next sections.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting an Event Manager │ │ │ │

│ │ │ │

To start an event manager for handling errors, as described in the previous │ │ │ │ example, call the following function:

gen_event:start_link({local, error_man})

gen_event:start_link/1 spawns and links to a new event manager process.

The argument, {local, error_man}, specifies the name under which the │ │ │ │ @@ -89,57 +89,57 @@ │ │ │ │ manager that is not part of a supervision tree.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding an Event Handler │ │ │ │

│ │ │ │

The following example shows how to start an event manager and add an event │ │ │ │ -handler to it by using the shell:

1> gen_event:start({local, error_man}).
│ │ │ │ -{ok,<0.31.0>}
│ │ │ │ -2> gen_event:add_handler(error_man, terminal_logger, []).
│ │ │ │ +handler to it by using the shell:

1> gen_event:start({local, error_man}).
│ │ │ │ +{ok,<0.31.0>}
│ │ │ │ +2> gen_event:add_handler(error_man, terminal_logger, []).
│ │ │ │  ok

This function sends a message to the event manager registered as error_man, │ │ │ │ telling it to add the event handler terminal_logger. The event manager calls │ │ │ │ the callback function terminal_logger:init([]), where the argument [] is the │ │ │ │ third argument to add_handler. init/1 is expected to return {ok, State}, │ │ │ │ -where State is the internal state of the event handler.

init(_Args) ->
│ │ │ │ -    {ok, []}.

Here, init/1 does not need any input data and ignores its argument. For │ │ │ │ +where State is the internal state of the event handler.

init(_Args) ->
│ │ │ │ +    {ok, []}.

Here, init/1 does not need any input data and ignores its argument. For │ │ │ │ terminal_logger, the internal state is not used. For file_logger, the │ │ │ │ -internal state is used to save the open file descriptor.

init(File) ->
│ │ │ │ -    {ok, Fd} = file:open(File, read),
│ │ │ │ -    {ok, Fd}.

│ │ │ │ +internal state is used to save the open file descriptor.

init(File) ->
│ │ │ │ +    {ok, Fd} = file:open(File, read),
│ │ │ │ +    {ok, Fd}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Notifying about Events │ │ │ │

│ │ │ │
3> gen_event:notify(error_man, no_reply).
│ │ │ │  ***Error*** no_reply
│ │ │ │  ok

error_man is the name of the event manager and no_reply is the event.

The event is made into a message and sent to the event manager. When the event │ │ │ │ is received, the event manager calls handle_event(Event, State) for each │ │ │ │ installed event handler, in the same order as they were added. The function is │ │ │ │ expected to return a tuple {ok,State1}, where State1 is a new value for the │ │ │ │ -state of the event handler.

In terminal_logger:

handle_event(ErrorMsg, State) ->
│ │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, State}.

In file_logger:

handle_event(ErrorMsg, Fd) ->
│ │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, Fd}.

│ │ │ │ +state of the event handler.

In terminal_logger:

handle_event(ErrorMsg, State) ->
│ │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, State}.

In file_logger:

handle_event(ErrorMsg, Fd) ->
│ │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, Fd}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deleting an Event Handler │ │ │ │

│ │ │ │ -
4> gen_event:delete_handler(error_man, terminal_logger, []).
│ │ │ │ +
4> gen_event:delete_handler(error_man, terminal_logger, []).
│ │ │ │  ok

This function sends a message to the event manager registered as error_man, │ │ │ │ telling it to delete the event handler terminal_logger. The event manager │ │ │ │ calls the callback function terminal_logger:terminate([], State), where the │ │ │ │ argument [] is the third argument to delete_handler. terminate/2 is to be │ │ │ │ the opposite of init/1 and do any necessary cleaning up. Its return value is │ │ │ │ -ignored.

For terminal_logger, no cleaning up is necessary:

terminate(_Args, _State) ->
│ │ │ │ -    ok.

For file_logger, the file descriptor opened in init must be closed:

terminate(_Args, Fd) ->
│ │ │ │ -    file:close(Fd).

│ │ │ │ +ignored.

For terminal_logger, no cleaning up is necessary:

terminate(_Args, _State) ->
│ │ │ │ +    ok.

For file_logger, the file descriptor opened in init must be closed:

terminate(_Args, Fd) ->
│ │ │ │ +    file:close(Fd).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │ │

│ │ │ │

When an event manager is stopped, it gives each of the installed event handlers │ │ │ │ the chance to clean up by calling terminate/2, the same way as when deleting a │ │ │ │ @@ -154,29 +154,29 @@ │ │ │ │ this is done is defined by a shutdown strategy set in │ │ │ │ the supervisor.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone Event Managers │ │ │ │

│ │ │ │ -

An event manager can also be stopped by calling:

1> gen_event:stop(error_man).
│ │ │ │ +

An event manager can also be stopped by calling:

1> gen_event:stop(error_man).
│ │ │ │  ok

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │ │

│ │ │ │

If the gen_event process is to be able to receive other messages │ │ │ │ than events, the callback function handle_info(Info, State) must be │ │ │ │ implemented to handle them. Examples of other messages are exit │ │ │ │ messages if the event manager is linked to other processes than the │ │ │ │ supervisor (for example via gen_event:add_sup_handler/3) and is │ │ │ │ -trapping exit signals.

handle_info({'EXIT', Pid, Reason}, State) ->
│ │ │ │ +trapping exit signals.

handle_info({'EXIT', Pid, Reason}, State) ->
│ │ │ │      %% Code to handle exits here.
│ │ │ │      ...
│ │ │ │ -    {noreply, State1}.

The final function to implement is code_change/3:

code_change(OldVsn, State, Extra) ->
│ │ │ │ +    {noreply, State1}.

The final function to implement is code_change/3:

code_change(OldVsn, State, Extra) ->
│ │ │ │      %% Code to convert state (and more) during code change.
│ │ │ │      ...
│ │ │ │ -    {ok, NewState}.
│ │ │ │ +
{ok, NewState}.
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/errors.xhtml │ │ │ │ @@ -56,22 +56,22 @@ │ │ │ │ classes, with different origins. The try expression can │ │ │ │ distinguish between the different classes, whereas the │ │ │ │ catch expression cannot. try and catch are described │ │ │ │ in Expressions.

ClassOrigin
errorRun-time error, for example, 1+a, or the process called error/1
exitThe process called exit/1
throwThe process called throw/1

Table: Exception Classes.

All of the above exceptions can also be generated by calling erlang:raise/3.

An exception consists of its class, an exit reason (see │ │ │ │ Exit Reason), and a stack trace (which aids in finding │ │ │ │ the code location of the exception).

The stack trace can be bound to a variable from within a try expression for │ │ │ │ any exception class, or as part of the exit reason when a run-time error is │ │ │ │ -caught by a catch. Example:

> {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
│ │ │ │ -[{shell,apply_fun,3,[]},
│ │ │ │ - {erl_eval,do_apply,6,[]},
│ │ │ │ - ...]
│ │ │ │ -> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
│ │ │ │ -[{shell,apply_fun,3,[]},
│ │ │ │ - {erl_eval,do_apply,6,[]},
│ │ │ │ - ...]

│ │ │ │ +caught by a catch. Example:

> {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
│ │ │ │ +[{shell,apply_fun,3,[]},
│ │ │ │ + {erl_eval,do_apply,6,[]},
│ │ │ │ + ...]
│ │ │ │ +> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
│ │ │ │ +[{shell,apply_fun,3,[]},
│ │ │ │ + {erl_eval,do_apply,6,[]},
│ │ │ │ + ...]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The call-stack back trace (stacktrace) │ │ │ │

│ │ │ │

The stack back-trace (stacktrace) is a list that │ │ │ │ contains {Module, Function, Arity, ExtraInfo} and/or {Fun, Arity, ExtraInfo} │ │ │ ├── OEBPS/error_logging.xhtml │ │ │ │ @@ -48,36 +48,36 @@ │ │ │ │ reports and other error and information reports are by default logged through │ │ │ │ the log handler which is set up when the Kernel application is started.

Prior to Erlang/OTP 21.0, supervisor, crash, and progress reports were only │ │ │ │ logged when the SASL application was running. This behaviour can, for backwards │ │ │ │ compatibility, be enabled by setting the Kernel configuration parameter │ │ │ │ logger_sasl_compatible to │ │ │ │ true. For more information, see │ │ │ │ SASL Error Logging in the SASL User's Guide.

% erl -kernel logger_level info
│ │ │ │ -Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
│ │ │ │ +Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
│ │ │ │  
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.916404 ===
│ │ │ │      application: kernel
│ │ │ │      started_at: nonode@nohost
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.922908 ===
│ │ │ │      application: stdlib
│ │ │ │      started_at: nonode@nohost
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.925755 ===
│ │ │ │ -    supervisor: {local,kernel_safe_sup}
│ │ │ │ -    started: [{pid,<0.74.0>},
│ │ │ │ -              {id,disk_log_sup},
│ │ │ │ -              {mfargs,{disk_log_sup,start_link,[]}},
│ │ │ │ -              {restart_type,permanent},
│ │ │ │ -              {shutdown,1000},
│ │ │ │ -              {child_type,supervisor}]
│ │ │ │ +    supervisor: {local,kernel_safe_sup}
│ │ │ │ +    started: [{pid,<0.74.0>},
│ │ │ │ +              {id,disk_log_sup},
│ │ │ │ +              {mfargs,{disk_log_sup,start_link,[]}},
│ │ │ │ +              {restart_type,permanent},
│ │ │ │ +              {shutdown,1000},
│ │ │ │ +              {child_type,supervisor}]
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.926056 ===
│ │ │ │ -    supervisor: {local,kernel_safe_sup}
│ │ │ │ -    started: [{pid,<0.75.0>},
│ │ │ │ -              {id,disk_log_server},
│ │ │ │ -              {mfargs,{disk_log_server,start_link,[]}},
│ │ │ │ -              {restart_type,permanent},
│ │ │ │ -              {shutdown,2000},
│ │ │ │ -              {child_type,worker}]
│ │ │ │ -Eshell V10.0  (abort with ^G)
│ │ │ │ +    supervisor: {local,kernel_safe_sup}
│ │ │ │ +    started: [{pid,<0.75.0>},
│ │ │ │ +              {id,disk_log_server},
│ │ │ │ +              {mfargs,{disk_log_server,start_link,[]}},
│ │ │ │ +              {restart_type,permanent},
│ │ │ │ +              {shutdown,2000},
│ │ │ │ +              {child_type,worker}]
│ │ │ │ +Eshell V10.0  (abort with ^G)
│ │ │ │  1>
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/erl_interface.xhtml │ │ │ │ @@ -25,119 +25,119 @@ │ │ │ │ to read the port example in Ports before reading this section.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Erlang Program │ │ │ │

│ │ │ │

The following example shows an Erlang program communicating with a C program │ │ │ │ -over a plain port with home made encoding:

-module(complex1).
│ │ │ │ --export([start/1, stop/0, init/1]).
│ │ │ │ --export([foo/1, bar/1]).
│ │ │ │ -
│ │ │ │ -start(ExtPrg) ->
│ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ -stop() ->
│ │ │ │ +over a plain port with home made encoding:

-module(complex1).
│ │ │ │ +-export([start/1, stop/0, init/1]).
│ │ │ │ +-export([foo/1, bar/1]).
│ │ │ │ +
│ │ │ │ +start(ExtPrg) ->
│ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ +stop() ->
│ │ │ │      complex ! stop.
│ │ │ │  
│ │ │ │ -foo(X) ->
│ │ │ │ -    call_port({foo, X}).
│ │ │ │ -bar(Y) ->
│ │ │ │ -    call_port({bar, Y}).
│ │ │ │ +foo(X) ->
│ │ │ │ +    call_port({foo, X}).
│ │ │ │ +bar(Y) ->
│ │ │ │ +    call_port({bar, Y}).
│ │ │ │  
│ │ │ │ -call_port(Msg) ->
│ │ │ │ -    complex ! {call, self(), Msg},
│ │ │ │ +call_port(Msg) ->
│ │ │ │ +    complex ! {call, self(), Msg},
│ │ │ │      receive
│ │ │ │ -	{complex, Result} ->
│ │ │ │ +	{complex, Result} ->
│ │ │ │  	    Result
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -init(ExtPrg) ->
│ │ │ │ -    register(complex, self()),
│ │ │ │ -    process_flag(trap_exit, true),
│ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
│ │ │ │ -    loop(Port).
│ │ │ │ +init(ExtPrg) ->
│ │ │ │ +    register(complex, self()),
│ │ │ │ +    process_flag(trap_exit, true),
│ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
│ │ │ │ +    loop(Port).
│ │ │ │  
│ │ │ │ -loop(Port) ->
│ │ │ │ +loop(Port) ->
│ │ │ │      receive
│ │ │ │ -	{call, Caller, Msg} ->
│ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
│ │ │ │ +	{call, Caller, Msg} ->
│ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
│ │ │ │  	    receive
│ │ │ │ -		{Port, {data, Data}} ->
│ │ │ │ -		    Caller ! {complex, decode(Data)}
│ │ │ │ +		{Port, {data, Data}} ->
│ │ │ │ +		    Caller ! {complex, decode(Data)}
│ │ │ │  	    end,
│ │ │ │ -	    loop(Port);
│ │ │ │ +	    loop(Port);
│ │ │ │  	stop ->
│ │ │ │ -	    Port ! {self(), close},
│ │ │ │ +	    Port ! {self(), close},
│ │ │ │  	    receive
│ │ │ │ -		{Port, closed} ->
│ │ │ │ -		    exit(normal)
│ │ │ │ +		{Port, closed} ->
│ │ │ │ +		    exit(normal)
│ │ │ │  	    end;
│ │ │ │ -	{'EXIT', Port, Reason} ->
│ │ │ │ -	    exit(port_terminated)
│ │ │ │ +	{'EXIT', Port, Reason} ->
│ │ │ │ +	    exit(port_terminated)
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -encode({foo, X}) -> [1, X];
│ │ │ │ -encode({bar, Y}) -> [2, Y].
│ │ │ │ +encode({foo, X}) -> [1, X];
│ │ │ │ +encode({bar, Y}) -> [2, Y].
│ │ │ │  
│ │ │ │ -decode([Int]) -> Int.

There are two differences when using Erl_Interface on the C side compared to the │ │ │ │ +decode([Int]) -> Int.

There are two differences when using Erl_Interface on the C side compared to the │ │ │ │ example in Ports, using only the plain port:

  • As Erl_Interface operates on the Erlang external term format, the port must be │ │ │ │ set to use binaries.
  • Instead of inventing an encoding/decoding scheme, the │ │ │ │ term_to_binary/1 and │ │ │ │ -binary_to_term/1 BIFs are to be used.

That is:

open_port({spawn, ExtPrg}, [{packet, 2}])

is replaced with:

open_port({spawn, ExtPrg}, [{packet, 2}, binary])

And:

Port ! {self(), {command, encode(Msg)}},
│ │ │ │ +binary_to_term/1 BIFs are to be used.

That is:

open_port({spawn, ExtPrg}, [{packet, 2}])

is replaced with:

open_port({spawn, ExtPrg}, [{packet, 2}, binary])

And:

Port ! {self(), {command, encode(Msg)}},
│ │ │ │  receive
│ │ │ │ -  {Port, {data, Data}} ->
│ │ │ │ -    Caller ! {complex, decode(Data)}
│ │ │ │ -end

is replaced with:

Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │ +  {Port, {data, Data}} ->
│ │ │ │ +    Caller ! {complex, decode(Data)}
│ │ │ │ +end

is replaced with:

Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │  receive
│ │ │ │ -  {Port, {data, Data}} ->
│ │ │ │ -    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ -end

The resulting Erlang program is as follows:

-module(complex2).
│ │ │ │ --export([start/1, stop/0, init/1]).
│ │ │ │ --export([foo/1, bar/1]).
│ │ │ │ -
│ │ │ │ -start(ExtPrg) ->
│ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ -stop() ->
│ │ │ │ +  {Port, {data, Data}} ->
│ │ │ │ +    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ +end

The resulting Erlang program is as follows:

-module(complex2).
│ │ │ │ +-export([start/1, stop/0, init/1]).
│ │ │ │ +-export([foo/1, bar/1]).
│ │ │ │ +
│ │ │ │ +start(ExtPrg) ->
│ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ +stop() ->
│ │ │ │      complex ! stop.
│ │ │ │  
│ │ │ │ -foo(X) ->
│ │ │ │ -    call_port({foo, X}).
│ │ │ │ -bar(Y) ->
│ │ │ │ -    call_port({bar, Y}).
│ │ │ │ +foo(X) ->
│ │ │ │ +    call_port({foo, X}).
│ │ │ │ +bar(Y) ->
│ │ │ │ +    call_port({bar, Y}).
│ │ │ │  
│ │ │ │ -call_port(Msg) ->
│ │ │ │ -    complex ! {call, self(), Msg},
│ │ │ │ +call_port(Msg) ->
│ │ │ │ +    complex ! {call, self(), Msg},
│ │ │ │      receive
│ │ │ │ -	{complex, Result} ->
│ │ │ │ +	{complex, Result} ->
│ │ │ │  	    Result
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -init(ExtPrg) ->
│ │ │ │ -    register(complex, self()),
│ │ │ │ -    process_flag(trap_exit, true),
│ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
│ │ │ │ -    loop(Port).
│ │ │ │ +init(ExtPrg) ->
│ │ │ │ +    register(complex, self()),
│ │ │ │ +    process_flag(trap_exit, true),
│ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
│ │ │ │ +    loop(Port).
│ │ │ │  
│ │ │ │ -loop(Port) ->
│ │ │ │ +loop(Port) ->
│ │ │ │      receive
│ │ │ │ -	{call, Caller, Msg} ->
│ │ │ │ -	    Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │ +	{call, Caller, Msg} ->
│ │ │ │ +	    Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │  	    receive
│ │ │ │ -		{Port, {data, Data}} ->
│ │ │ │ -		    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ +		{Port, {data, Data}} ->
│ │ │ │ +		    Caller ! {complex, binary_to_term(Data)}
│ │ │ │  	    end,
│ │ │ │ -	    loop(Port);
│ │ │ │ +	    loop(Port);
│ │ │ │  	stop ->
│ │ │ │ -	    Port ! {self(), close},
│ │ │ │ +	    Port ! {self(), close},
│ │ │ │  	    receive
│ │ │ │ -		{Port, closed} ->
│ │ │ │ -		    exit(normal)
│ │ │ │ +		{Port, closed} ->
│ │ │ │ +		    exit(normal)
│ │ │ │  	    end;
│ │ │ │ -	{'EXIT', Port, Reason} ->
│ │ │ │ -	    exit(port_terminated)
│ │ │ │ +	{'EXIT', Port, Reason} ->
│ │ │ │ +	    exit(port_terminated)
│ │ │ │      end.

Notice that calling complex2:foo/1 and complex2:bar/1 results in the tuple │ │ │ │ {foo,X} or {bar,Y} being sent to the complex process, which codes them as │ │ │ │ binaries and sends them to the port. This means that the C program must be able │ │ │ │ to handle these two tuples.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -267,24 +267,24 @@ │ │ │ │ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ │ │ │ │ complex.c erl_comm.c ei.c -lei -lpthread

In Erlang/OTP R5B and later versions of OTP, the include and lib directories │ │ │ │ are situated under $OTPROOT/lib/erl_interface-VSN, where $OTPROOT is the │ │ │ │ root directory of the OTP installation (/usr/local/otp in the recent example) │ │ │ │ and VSN is the version of the Erl_interface application (3.2.1 in the recent │ │ │ │ example).

In R4B and earlier versions of OTP, include and lib are situated under │ │ │ │ $OTPROOT/usr.

Step 2. Start Erlang and compile the Erlang code:

$ erl
│ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │  
│ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> c(complex2).
│ │ │ │ -{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> c(complex2).
│ │ │ │ +{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │  <0.34.0>
│ │ │ │ -3> complex2:foo(3).
│ │ │ │ +3> complex2:foo(3).
│ │ │ │  4
│ │ │ │ -4> complex2:bar(5).
│ │ │ │ +4> complex2:bar(5).
│ │ │ │  10
│ │ │ │ -5> complex2:bar(352).
│ │ │ │ +5> complex2:bar(352).
│ │ │ │  704
│ │ │ │ -6> complex2:stop().
│ │ │ │ +6> complex2:stop().
│ │ │ │  stop
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_processes.xhtml │ │ │ │ @@ -24,45 +24,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │ │

│ │ │ │

An Erlang process is lightweight compared to threads and processes in operating │ │ │ │ systems.

A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ │ -as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │ +as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │  
│ │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │  #Fun<erl_eval.43.39164016>
│ │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ -{memory,2616}
│ │ │ │ -3> Bytes div erlang:system_info(wordsize).
│ │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ +{memory,2616}
│ │ │ │ +3> Bytes div erlang:system_info(wordsize).
│ │ │ │  327

The size includes 233 words for the heap area (which includes the stack). The │ │ │ │ garbage collector increases the heap as needed.

The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ │ -stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │ +stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │    receive
│ │ │ │ -     {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -     {From, Msg} ->
│ │ │ │ -          Reply = handle_msg(Msg),
│ │ │ │ +     {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +     {From, Msg} ->
│ │ │ │ +          Reply = handle_msg(Msg),
│ │ │ │            From ! Reply,
│ │ │ │ -          loop()
│ │ │ │ +          loop()
│ │ │ │    end,
│ │ │ │ -  io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ + io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ │ -correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │ +correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │     receive
│ │ │ │ -      {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -      {From, Msg} ->
│ │ │ │ -         Reply = handle_msg(Msg),
│ │ │ │ +      {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +      {From, Msg} ->
│ │ │ │ +         Reply = handle_msg(Msg),
│ │ │ │           From ! Reply,
│ │ │ │ -         loop()
│ │ │ │ +         loop()
│ │ │ │   end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │ │

│ │ │ │

The default initial heap size of 233 words is quite conservative to support │ │ │ │ @@ -94,30 +94,30 @@ │ │ │ │ │ │ │ │ │ │ │ │ Receiving messages │ │ │ │ │ │ │ │

The cost of receiving messages depends on how complicated the receive │ │ │ │ expression is. A simple expression that matches any message is very cheap │ │ │ │ because it retrieves the first message in the message queue:

DO

receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.

However, this is not always convenient: we can receive a message that we do not │ │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ │ expect:

receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.

While this is convenient it means that the entire message queue must be searched │ │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ │ long message queues, so there is an optimization for the common case of │ │ │ │ -sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ -Process ! {self(), MRef, Request},
│ │ │ │ +sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ +Process ! {self(), MRef, Request},
│ │ │ │  receive
│ │ │ │ -    {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -        handle_error(Reason)
│ │ │ │ +    {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +        handle_error(Reason)
│ │ │ │  end.

Since the compiler knows that the reference created by │ │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ │ arrived after the call to monitor/2.

The above is a simple example where one is but guaranteed that the optimization │ │ │ │ will take, but what about more complicated code?

│ │ │ │ │ │ │ │ @@ -133,101 +133,101 @@ │ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ │ example:

%% DO
│ │ │ │ -simple_receive() ->
│ │ │ │ +simple_receive() ->
│ │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
│ │ │ │  receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
│ │ │ │  %% cross_function_receive/0 further down.
│ │ │ │ -selective_receive(Tag, Message) ->
│ │ │ │ +selective_receive(Tag, Message) ->
│ │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
│ │ │ │  receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -optimized_receive(Process, Request) ->
│ │ │ │ +optimized_receive(Process, Request) ->
│ │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    MRef = monitor(process, Process),
│ │ │ │ -    Process ! {self(), MRef, Request},
│ │ │ │ +    MRef = monitor(process, Process),
│ │ │ │ +    Process ! {self(), MRef, Request},
│ │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
│ │ │ │      receive
│ │ │ │ -        {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -    handle_error(Reason)
│ │ │ │ +        {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +    handle_error(Reason)
│ │ │ │      end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -cross_function_receive() ->
│ │ │ │ +cross_function_receive() ->
│ │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    Ref = make_ref(),
│ │ │ │ +    Ref = make_ref(),
│ │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
│ │ │ │ -    cross_function_receive(Ref).
│ │ │ │ +    cross_function_receive(Ref).
│ │ │ │  
│ │ │ │ -cross_function_receive(Ref) ->
│ │ │ │ +cross_function_receive(Ref) ->
│ │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
│ │ │ │      receive
│ │ │ │ -        {Ref, Message} -> handle_msg(Message)
│ │ │ │ +        {Ref, Message} -> handle_msg(Message)
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │ │

│ │ │ │

Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ │ -pool:

DO

days_in_month(M) ->
│ │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ +pool:

DO

days_in_month(M) ->
│ │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ │ unloaded in the future.

When a literal is sent to another process, it is not copied. When a module │ │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ │ processes that hold references to that literal.

There also exists a global literal pool that is managed by the │ │ │ │ persistent_term module.

By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ │ for literals can be changed by using the │ │ │ │ +MIscs option when starting the emulator.

Here is an example how the reserved virtual address space for literals can be │ │ │ │ raised to 2 GB (2048 MB):

erl +MIscs 2048

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │ │

│ │ │ │ -

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ -subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ -    kilo_byte(10, [42]).
│ │ │ │ +

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ +subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ +    kilo_byte(10, [42]).
│ │ │ │  
│ │ │ │ -kilo_byte(0, Acc) ->
│ │ │ │ +kilo_byte(0, Acc) ->
│ │ │ │      Acc;
│ │ │ │ -kilo_byte(N, Acc) ->
│ │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ +kilo_byte(N, Acc) -> │ │ │ │ + kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
│ │ │ │  1024

Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ │ -requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │ +requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │  22

Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ │ -been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │ +been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │  4094

It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ │ -table:

4> T = ets:new(tab, []).
│ │ │ │ +table:

4> T = ets:new(tab, []).
│ │ │ │  #Ref<0.1662103692.2407923716.214181>
│ │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │  true
│ │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094
│ │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094

When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

It is possible to build an experimental variant of the runtime system that │ │ │ │ will preserve sharing when copying terms by giving the │ │ │ │ --enable-sharing-preserving option to the configure script.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_functions.xhtml │ │ │ │ @@ -27,67 +27,67 @@ │ │ │ │ Pattern Matching │ │ │ │

│ │ │ │

Pattern matching in function head as well as in case and receive clauses are │ │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ │ rearranging clauses.

One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ │ binary last is usually slightly faster than placing it first.

The following is a rather unnatural example to show another exception where │ │ │ │ -rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ -atom_map1(two) -> 2;
│ │ │ │ -atom_map1(three) -> 3;
│ │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map1(four) -> 4;
│ │ │ │ -atom_map1(five) -> 5;
│ │ │ │ -atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ +rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ +atom_map1(two) -> 2;
│ │ │ │ +atom_map1(three) -> 3;
│ │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map1(four) -> 4;
│ │ │ │ +atom_map1(five) -> 5;
│ │ │ │ +atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ │ follows:

  • First, the input value is compared to one, two, and three (using a │ │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ │ there are many values) to select which one of the first three clauses to │ │ │ │ execute (if any).
  • If none of the first three clauses match, the fourth clause match as a │ │ │ │ variable always matches.
  • If the guard test is_integer(Int) succeeds, the fourth │ │ │ │ clause is executed.
  • If the guard test fails, the input value is compared to four, five, and │ │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ │ -exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ -atom_map2(two) -> 2;
│ │ │ │ -atom_map2(three) -> 3;
│ │ │ │ -atom_map2(four) -> 4;
│ │ │ │ -atom_map2(five) -> 5;
│ │ │ │ -atom_map2(six) -> 6;
│ │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map3(one) -> 1;
│ │ │ │ -atom_map3(two) -> 2;
│ │ │ │ -atom_map3(three) -> 3;
│ │ │ │ -atom_map3(four) -> 4;
│ │ │ │ -atom_map3(five) -> 5;
│ │ │ │ -atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │ +exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ +atom_map2(two) -> 2;
│ │ │ │ +atom_map2(three) -> 3;
│ │ │ │ +atom_map2(four) -> 4;
│ │ │ │ +atom_map2(five) -> 5;
│ │ │ │ +atom_map2(six) -> 6;
│ │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map3(one) -> 1;
│ │ │ │ +atom_map3(two) -> 2;
│ │ │ │ +atom_map3(three) -> 3;
│ │ │ │ +atom_map3(four) -> 4;
│ │ │ │ +atom_map3(five) -> 5;
│ │ │ │ +atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs1(_Map, Xs, []) ->
│ │ │ │ +map_pairs1(_Map, Xs, []) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ │ the order written.

If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ │ -clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │ +clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │      case Xs0 of
│ │ │ │ -	[X|Xs] ->
│ │ │ │ +	[X|Xs] ->
│ │ │ │  	    case Ys0 of
│ │ │ │ -		[Y|Ys] ->
│ │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ -		[] ->
│ │ │ │ +		[Y|Ys] ->
│ │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ +		[] ->
│ │ │ │  		    Xs0
│ │ │ │  	    end;
│ │ │ │ -	[] ->
│ │ │ │ +	[] ->
│ │ │ │  	    Ys0
│ │ │ │      end.

This is slightly faster for probably the most common case that the input lists │ │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ │ better type for the Xs variable.)

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/drivers.xhtml │ │ │ │ @@ -27,23 +27,23 @@ │ │ │ │ Drivers and Concurrency │ │ │ │

│ │ │ │

The runtime system always takes a lock before running any code in a driver.

By default, that lock is at the driver level, that is, if several ports have │ │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ │ running.

A driver can be configured to have one lock for each port instead.

If a driver is used in a functional way (that is, holds no state, but only does │ │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ │ -the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │ +the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
│ │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
│ │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │  
│ │ │ │ -client_port() ->
│ │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
│ │ │ │ -	    ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ +client_port() -> │ │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ │ + ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ contention on the port lock for the driver.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │ │

│ │ │ │

There are basically two ways to avoid copying a binary that is sent to a driver:

  • If the Data argument for port_control/3 is a │ │ │ ├── OEBPS/documentation.xhtml │ │ │ │ @@ -17,23 +17,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │

    │ │ │ │ Documentation │ │ │ │

    │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ │ +attributes. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ │ attribute it documents. The │ │ │ │ attributes that can be documented are │ │ │ │ user-defined types │ │ │ │ (-type and -opaque) and │ │ │ │ behaviour module attributes │ │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ │ @@ -45,55 +45,55 @@ │ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │ │

    │ │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ --doc(#{since => "1.0"}).
    │ │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ +-doc(#{since => "1.0"}). │ │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ │ --doc #{since => "2.0"}.
    │ │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ │ +-doc #{since => "2.0"}. │ │ │ │ +add(One, Two) -> One + Two.

This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ that only atoms are used for keys and │ │ │ │ strings for the values.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │ │

│ │ │ │

The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ │ relative to the file where the -doc attribute is located. For example:

%% doc/add.md
│ │ │ │  Adds two numbers.

and

%% src/arith.erl
│ │ │ │ --doc({file, "../doc/add.md"}).
│ │ │ │ -add(One, Two) -> One + Two.

│ │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ │ +add(One, Two) -> One + Two.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │ │

│ │ │ │

The module description should include details on how to use the API and examples │ │ │ │ of the different functions working together. Here is a good place to use images │ │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ │ an external page.

The moduledoc attribute should start with a short paragraph describing the │ │ │ │ -module and then go into greater details. For example:

-module(arith).
│ │ │ │ +module and then go into greater details. For example:

-module(arith).
│ │ │ │  -moduledoc """
│ │ │ │     A module for basic arithmetic.
│ │ │ │  
│ │ │ │     This module can be used to add and subtract values. For example:
│ │ │ │  
│ │ │ │     ```erlang
│ │ │ │     1> arith:substract(arith:add(2, 3), 1).
│ │ │ │ @@ -108,94 +108,94 @@
│ │ │ │  

There are three reserved metadata keys for -moduledoc:

  • since - Shows in which version of the application the module was added. │ │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ │ or callback.
  • deprecated - Shows a text in the documentation explaining that it is │ │ │ │ deprecated and what to use instead.
  • format - The format to use for all documentation in this module. The │ │ │ │ default is text/markdown. It should be written using the │ │ │ │ mime type │ │ │ │ -of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ +of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │ │

│ │ │ │

Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ │ and then go into greater detail in needed.

It is not recommended to include images or diagrams in this documentation as it │ │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

For example:

-doc """
│ │ │ │  A number that can be used by the arith module.
│ │ │ │  
│ │ │ │  We use a special number here so that we know
│ │ │ │  that this number comes from this module.
│ │ │ │  """.
│ │ │ │ --opaque number() :: {arith, erlang:number()}.
│ │ │ │ +-opaque number() :: {arith, erlang:number()}.
│ │ │ │  
│ │ │ │  -doc """
│ │ │ │  Adds two numbers.
│ │ │ │  
│ │ │ │  ### Example:
│ │ │ │  
│ │ │ │  ```
│ │ │ │  1> arith:add(arith:number(1), arith:number(2)). {number, 3}
│ │ │ │  ```
│ │ │ │  """.
│ │ │ │ --spec add(number(), number()) -> number().
│ │ │ │ -add({number, One}, {number, Two}) -> {number, One + Two}.

│ │ │ │ +-spec add(number(), number()) -> number(). │ │ │ │ +add({number, One}, {number, Two}) -> {number, One + Two}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Doc metadata │ │ │ │

│ │ │ │

There are four reserved metadata keys for -doc:

  • since => unicode:chardata() - Shows which version of the application the │ │ │ │ module was added.

  • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ │ function as deprecated.

  • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ │ another function in this module. The equivalence can be described using either │ │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ --spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ the value converted to a string.

  • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ │ by the user.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │ │

│ │ │ │

The doc signature is a short text shown to describe the function and its arguments. │ │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ │ --spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │ +-spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │  
│ │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
│ │ │ │ -sub(X, Y) -> X - Y.

will have a signature of add(One, Two) and sub(One, Two).

For types or callbacks, the signature is derived from the type or callback │ │ │ │ -specification. For example:

-type number(Value) :: {number, Value}.
│ │ │ │ +-spec sub(One :: integer(), Two :: integer()) -> integer().
│ │ │ │ +sub(X, Y) -> X - Y.

will have a signature of add(One, Two) and sub(One, Two).

For types or callbacks, the signature is derived from the type or callback │ │ │ │ +specification. For example:

-type number(Value) :: {number, Value}.
│ │ │ │  %% signature will be `number(Value)`
│ │ │ │  
│ │ │ │ --opaque number() :: {number, number()}.
│ │ │ │ +-opaque number() :: {number, number()}.
│ │ │ │  %% signature will be `number()`
│ │ │ │  
│ │ │ │ --callback increment(In :: number()) -> Out.
│ │ │ │ +-callback increment(In :: number()) -> Out.
│ │ │ │  %% signature will be `increment(In)`
│ │ │ │  
│ │ │ │ --callback increment(In) -> Out when In :: number().
│ │ │ │ +-callback increment(In) -> Out when In :: number().
│ │ │ │  %% signature will be `increment(In)`

If it is not possible to "easily" figure out a nice signature from the code, the │ │ │ │ MFA syntax is used instead. For example: add/2, number/1, increment/1

It is possible to supply a custom signature by placing it as the first line of the │ │ │ │ -doc attribute. The provided signature must be in the form of a function │ │ │ │ declaration up until the ->. For example:

-doc """
│ │ │ │  add(One, Two)
│ │ │ │  
│ │ │ │  Adds two numbers.
│ │ │ │  """.
│ │ │ │ -add(A, B) -> A + B.

Will create the signature add(One, Two). The signature will be removed from the │ │ │ │ +add(A, B) -> A + B.

Will create the signature add(One, Two). The signature will be removed from the │ │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ │ callbacks.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ │ @@ -280,21 +280,21 @@ │ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │ │

│ │ │ │

ExDoc has built-in support to generate │ │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ │ rebar3 project to use ExDoc to generate │ │ │ │ documentation add the following to your rebar3.config.

%% Enable the plugin
│ │ │ │ -{plugins, [rebar3_ex_doc]}.
│ │ │ │ +{plugins, [rebar3_ex_doc]}.
│ │ │ │  
│ │ │ │ -{ex_doc, [
│ │ │ │ -  {extras, ["README.md"]},
│ │ │ │ -  {main, "README.md"},
│ │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
│ │ │ │ -]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ +{ex_doc, [ │ │ │ │ + {extras, ["README.md"]}, │ │ │ │ + {main, "README.md"}, │ │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ │ +]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ documentation to doc/index.html. For more details and options see │ │ │ │ the rebar3_ex_doc documentation.

You can also download the │ │ │ │ release escript bundle from │ │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ │ is found by running ex_doc --help.

If you are writing documentation that will be using │ │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ │ recommended to read its documentation.

│ │ │ ├── OEBPS/distributed_applications.xhtml │ │ │ │ @@ -55,36 +55,36 @@ │ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ │ -  ]
    │ │ │ │ - }
    │ │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ │ +  ]
    │ │ │ │ + }
    │ │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │ │

    │ │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ │ application can be started by calling application:start(Application) at all │ │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ │ started as usual. That is, an application master is created and calls the │ │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │ │  > erl -sname cp2 -config cp2
    │ │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ │ │ @@ -92,30 +92,30 @@ │ │ │ │ │ │ │ │ Failover │ │ │ │

    │ │ │ │

    If the node where the application is running goes down, the application is │ │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ Included Applications). The application is then │ │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │ │

    │ │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ │ the node where a distributed application is running, the application is │ │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/distributed.xhtml │ │ │ │ @@ -47,25 +47,25 @@ │ │ │ │ │ │ │ │

    A node is an executing Erlang runtime system that has been given a name, using │ │ │ │ the command-line flag -name (long names) or │ │ │ │ -sname (short names).

    The format of the node name is an atom name@host. name is the name given by │ │ │ │ the user. host is the full host name if long names are used, or the first part │ │ │ │ of the host name if short names are used. Function node() │ │ │ │ returns the name of the node.

    Example:

    % erl -name dilbert
    │ │ │ │ -(dilbert@uab.ericsson.se)1> node().
    │ │ │ │ +(dilbert@uab.ericsson.se)1> node().
    │ │ │ │  'dilbert@uab.ericsson.se'
    │ │ │ │  
    │ │ │ │  % erl -sname dilbert
    │ │ │ │ -(dilbert@uab)1> node().
    │ │ │ │ +(dilbert@uab)1> node().
    │ │ │ │  dilbert@uab

    The node name can also be given in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ │ -1> node().
    │ │ │ │ +1> node().
    │ │ │ │  nonode@nohost
    │ │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ -{ok,<0.102.0>}
    │ │ │ │ -(dilbert@uab)3> node().
    │ │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ +{ok,<0.102.0>}
    │ │ │ │ +(dilbert@uab)3> node().
    │ │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │ │

    │ │ │ ├── OEBPS/design_principles.xhtml │ │ │ │ @@ -57,135 +57,135 @@ │ │ │ │ the code for a process in a generic part (a behaviour module) and a specific │ │ │ │ part (a callback module).

    The behaviour module is part of Erlang/OTP. To implement a process such as a │ │ │ │ supervisor, the user only needs to implement the callback module, which is to │ │ │ │ export a pre-defined set of functions, the callback functions.

    The following example illustrate how code can be divided into a generic and a │ │ │ │ specific part. Consider the following code (written in plain Erlang) for a │ │ │ │ simple server, which keeps track of a number of "channels". Other processes can │ │ │ │ allocate and free the channels by calling the functions alloc/0 and free/1, │ │ │ │ -respectively.

    -module(ch1).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0]).
    │ │ │ │ +respectively.

    -module(ch1).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0]).
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(ch1, init, []).
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(ch1, init, []).
    │ │ │ │  
    │ │ │ │ -alloc() ->
    │ │ │ │ -    ch1 ! {self(), alloc},
    │ │ │ │ +alloc() ->
    │ │ │ │ +    ch1 ! {self(), alloc},
    │ │ │ │      receive
    │ │ │ │ -        {ch1, Res} ->
    │ │ │ │ +        {ch1, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    ch1 ! {free, Ch},
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    ch1 ! {free, Ch},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    register(ch1, self()),
    │ │ │ │ -    Chs = channels(),
    │ │ │ │ -    loop(Chs).
    │ │ │ │ +init() ->
    │ │ │ │ +    register(ch1, self()),
    │ │ │ │ +    Chs = channels(),
    │ │ │ │ +    loop(Chs).
    │ │ │ │  
    │ │ │ │ -loop(Chs) ->
    │ │ │ │ +loop(Chs) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, alloc} ->
    │ │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -            From ! {ch1, Ch},
    │ │ │ │ -            loop(Chs2);
    │ │ │ │ -        {free, Ch} ->
    │ │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ │ -            loop(Chs2)
    │ │ │ │ -    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ --export([start/1]).
    │ │ │ │ --export([call/2, cast/2]).
    │ │ │ │ --export([init/1]).
    │ │ │ │ +        {From, alloc} ->
    │ │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +            From ! {ch1, Ch},
    │ │ │ │ +            loop(Chs2);
    │ │ │ │ +        {free, Ch} ->
    │ │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ │ +            loop(Chs2)
    │ │ │ │ +    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ +-export([start/1]).
    │ │ │ │ +-export([call/2, cast/2]).
    │ │ │ │ +-export([init/1]).
    │ │ │ │  
    │ │ │ │ -start(Mod) ->
    │ │ │ │ -    spawn(server, init, [Mod]).
    │ │ │ │ +start(Mod) ->
    │ │ │ │ +    spawn(server, init, [Mod]).
    │ │ │ │  
    │ │ │ │ -call(Name, Req) ->
    │ │ │ │ -    Name ! {call, self(), Req},
    │ │ │ │ +call(Name, Req) ->
    │ │ │ │ +    Name ! {call, self(), Req},
    │ │ │ │      receive
    │ │ │ │ -        {Name, Res} ->
    │ │ │ │ +        {Name, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -cast(Name, Req) ->
    │ │ │ │ -    Name ! {cast, Req},
    │ │ │ │ +cast(Name, Req) ->
    │ │ │ │ +    Name ! {cast, Req},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init(Mod) ->
    │ │ │ │ -    register(Mod, self()),
    │ │ │ │ -    State = Mod:init(),
    │ │ │ │ -    loop(Mod, State).
    │ │ │ │ +init(Mod) ->
    │ │ │ │ +    register(Mod, self()),
    │ │ │ │ +    State = Mod:init(),
    │ │ │ │ +    loop(Mod, State).
    │ │ │ │  
    │ │ │ │ -loop(Mod, State) ->
    │ │ │ │ +loop(Mod, State) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, From, Req} ->
    │ │ │ │ -            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ -            From ! {Mod, Res},
    │ │ │ │ -            loop(Mod, State2);
    │ │ │ │ -        {cast, Req} ->
    │ │ │ │ -            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ -            loop(Mod, State2)
    │ │ │ │ -    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    server:start(ch2).
    │ │ │ │ -
    │ │ │ │ -alloc() ->
    │ │ │ │ -    server:call(ch2, alloc).
    │ │ │ │ -
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    server:cast(ch2, {free, Ch}).
    │ │ │ │ +        {call, From, Req} ->
    │ │ │ │ +            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ +            From ! {Mod, Res},
    │ │ │ │ +            loop(Mod, State2);
    │ │ │ │ +        {cast, Req} ->
    │ │ │ │ +            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ +            loop(Mod, State2)
    │ │ │ │ +    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    server:start(ch2).
    │ │ │ │ +
    │ │ │ │ +alloc() ->
    │ │ │ │ +    server:call(ch2, alloc).
    │ │ │ │ +
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    server:cast(ch2, {free, Ch}).
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    channels().
    │ │ │ │ +init() ->
    │ │ │ │ +    channels().
    │ │ │ │  
    │ │ │ │ -handle_call(alloc, Chs) ->
    │ │ │ │ -    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │ +handle_call(alloc, Chs) ->
    │ │ │ │ +    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │  
    │ │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ the client functions. This means that the name can be changed without │ │ │ │ affecting them.
    • The protocol (messages sent to and received from the server) is also hidden. │ │ │ │ This is good programming practice and allows one to change the protocol │ │ │ │ without changing the code using the interface functions.
    • The functionality of server can be extended without having to change ch2 │ │ │ │ or any other callback module.

    In ch1.erl and ch2.erl above, the implementation of channels/0, alloc/1, │ │ │ │ and free/2 has been intentionally left out, as it is not relevant to the │ │ │ │ example. For completeness, one way to write these functions is given below. This │ │ │ │ is an example only, a realistic implementation must be able to handle situations │ │ │ │ -like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ -   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │ +like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ +   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │  
    │ │ │ │ -alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ -   {H, {[H|Allocated], T}}.
    │ │ │ │ +alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ +   {H, {[H|Allocated], T}}.
    │ │ │ │  
    │ │ │ │ -free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ -   case lists:member(Ch, Alloc) of
    │ │ │ │ +free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ +   case lists:member(Ch, Alloc) of
    │ │ │ │        true ->
    │ │ │ │ -         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │ +         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │        false ->
    │ │ │ │           Channels
    │ │ │ │     end.

    Code written without using behaviours can be more efficient, but the increased │ │ │ │ efficiency is at the expense of generality. The ability to manage all │ │ │ │ applications in the system in a consistent manner is important.

    Using behaviours also makes it easier to read and understand code written by │ │ │ │ other programmers. Improvised programming structures, while possibly more │ │ │ │ efficient, are always more difficult to understand.

    The server module corresponds, greatly simplified, to the Erlang/OTP behaviour │ │ │ │ gen_server.

    The standard Erlang/OTP behaviours are:

    • gen_server

      For implementing the server of a client-server relation

    • gen_statem

      For implementing state machines

    • gen_event

      For implementing event handling functionality

    • supervisor

      For implementing a supervisor in a supervision tree

    The compiler understands the module attribute -behaviour(Behaviour) and issues │ │ │ │ -warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ --behaviour(gen_server).
    │ │ │ │ +warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ +-behaviour(gen_server).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -3> c(chs3).
    │ │ │ │ +3> c(chs3).
    │ │ │ │  ./chs3.erl:10: Warning: undefined call-back function handle_call/3
    │ │ │ │ -{ok,chs3}

    │ │ │ │ +{ok,chs3}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Applications │ │ │ │

    │ │ │ │

    Erlang/OTP comes with a number of components, each implementing some specific │ │ │ │ functionality. Components are with Erlang/OTP terminology called applications. │ │ │ ├── OEBPS/data_types.xhtml │ │ │ │ @@ -97,18 +97,18 @@ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │ │ │

    When working with floats you may not see what you expect when printing or doing │ │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

    1> 0.1+0.2.
    │ │ │ │ -0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ -{3.602879701896397e16, true,
    │ │ │ │ - 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ +{3.602879701896397e16, true,
    │ │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ │ (=36028797018963970.0) as all values in the range │ │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ │ @@ -142,52 +142,52 @@ │ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │ │  <<10,20>>
    │ │ │ │  2> <<"ABC">>.
    │ │ │ │  <<"ABC">>
    │ │ │ │  3> <<1:1,0:1>>.
    │ │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ │ term is a bit string, and the is_binary/1 │ │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │  true
    │ │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ │ +2> is_binary(<<1:1>>).
    │ │ │ │  false
    │ │ │ │ -3> is_binary(<<42>>).
    │ │ │ │ +3> is_binary(<<42>>).
    │ │ │ │  true
    │ │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │ │

    │ │ │ │

    A term that is unique │ │ │ │ among connected nodes. A reference is created by calling the │ │ │ │ make_ref/0 BIF. The │ │ │ │ is_reference/1 BIF tests whether a term │ │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ │ -2> is_reference(Ref).
    │ │ │ │ +2> is_reference(Ref).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │ │

    │ │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ │ -2> Fun1(2).
    │ │ │ │ +2> Fun1(2).
    │ │ │ │  3

    The is_function/1 and is_function/2 │ │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ │ -2> is_function(F).
    │ │ │ │ +2> is_function(F).
    │ │ │ │  true
    │ │ │ │ -3> is_function(F, 0).
    │ │ │ │ +3> is_function(F, 0).
    │ │ │ │  true
    │ │ │ │ -4> is_function(F, 1).
    │ │ │ │ +4> is_function(F, 1).
    │ │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │ │

    │ │ │ │ @@ -205,94 +205,94 @@ │ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ │ creating a new process, the parent │ │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ │ a message, which is the case when calling the │ │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ │ when sending a process a signal. The │ │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          who_are_you ->
    │ │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ │ -            loop()
    │ │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ │ +            loop()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ │ +1> P = spawn(m, loop, []).
    │ │ │ │  <0.58.0>
    │ │ │ │  2> P ! who_are_you.
    │ │ │ │  I am <0.58.0>
    │ │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │ │

    │ │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ -{adam,24,{july,29}}
    │ │ │ │ -2> element(1,P).
    │ │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ +{adam,24,{july,29}}
    │ │ │ │ +2> element(1,P).
    │ │ │ │  adam
    │ │ │ │ -3> element(3,P).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ │ -{adam,25,{july,29}}
    │ │ │ │ -5> tuple_size(P).
    │ │ │ │ +3> element(3,P).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ │ +{adam,25,{july,29}}
    │ │ │ │ +5> tuple_size(P).
    │ │ │ │  3
    │ │ │ │ -6> tuple_size({}).
    │ │ │ │ +6> tuple_size({}).
    │ │ │ │  0
    │ │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ │ +7> is_tuple({a,b,c}).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │ │

    │ │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ │ and value parts of the pair are called elements. The number of association │ │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ │ -2> maps:get(name, M1).
    │ │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ │ +2> maps:get(name, M1).
    │ │ │ │  adam
    │ │ │ │ -3> maps:get(date, M1).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ │ -5> map_size(M).
    │ │ │ │ +3> maps:get(date, M1).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ │ +5> map_size(M).
    │ │ │ │  3
    │ │ │ │ -6> map_size(#{}).
    │ │ │ │ +6> map_size(#{}).
    │ │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │ │

    │ │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ -2> [H|T] = L1.
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │ +2> [H|T] = L1.
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │  3> H.
    │ │ │ │  a
    │ │ │ │  4> T.
    │ │ │ │ -[2,{c,4}]
    │ │ │ │ -5> L2 = [d|T].
    │ │ │ │ -[d,2,{c,4}]
    │ │ │ │ -6> length(L1).
    │ │ │ │ +[2,{c,4}]
    │ │ │ │ +5> L2 = [d|T].
    │ │ │ │ +[d,2,{c,4}]
    │ │ │ │ +6> length(L1).
    │ │ │ │  3
    │ │ │ │ -7> length([]).
    │ │ │ │ +7> length([]).
    │ │ │ │  0

    A collection of list processing functions are found in module │ │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │ │

    │ │ │ │ @@ -412,41 +412,41 @@ │ │ │ │ Record │ │ │ │ │ │ │ │

    A record is a data structure for storing a fixed number of elements. It has │ │ │ │ named fields and is similar to a struct in C. However, a record is not a true │ │ │ │ data type. Instead, record expressions are translated to tuple expressions │ │ │ │ during compilation. Therefore, record expressions are not understood by the │ │ │ │ shell unless special actions are taken. For details, see module shell │ │ │ │ -in STDLIB.

    Examples:

    -module(person).
    │ │ │ │ --export([new/2]).
    │ │ │ │ +in STDLIB.

    Examples:

    -module(person).
    │ │ │ │ +-export([new/2]).
    │ │ │ │  
    │ │ │ │ --record(person, {name, age}).
    │ │ │ │ +-record(person, {name, age}).
    │ │ │ │  
    │ │ │ │ -new(Name, Age) ->
    │ │ │ │ -    #person{name=Name, age=Age}.
    │ │ │ │ +new(Name, Age) ->
    │ │ │ │ +    #person{name=Name, age=Age}.
    │ │ │ │  
    │ │ │ │ -1> person:new(ernie, 44).
    │ │ │ │ -{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ │ +1> person:new(ernie, 44). │ │ │ │ +{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ │ found in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Boolean │ │ │ │

    │ │ │ │

    There is no Boolean data type in Erlang. Instead the atoms true and false │ │ │ │ are used to denote Boolean values. The is_boolean/1 │ │ │ │ BIF tests whether a term is a boolean.

    Examples:

    1> 2 =< 3.
    │ │ │ │  true
    │ │ │ │  2> true or false.
    │ │ │ │  true
    │ │ │ │ -3> is_boolean(true).
    │ │ │ │ +3> is_boolean(true).
    │ │ │ │  true
    │ │ │ │ -4> is_boolean(false).
    │ │ │ │ +4> is_boolean(false).
    │ │ │ │  true
    │ │ │ │ -5> is_boolean(ok).
    │ │ │ │ +5> is_boolean(ok).
    │ │ │ │  false

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Escape Sequences │ │ │ │

    │ │ │ │

    Within strings ("-delimited), quoted atoms, and the content of │ │ │ │ @@ -464,44 +464,44 @@ │ │ │ │ ~b or ~s sigils the escape sequences for normal │ │ │ │ strings, above, are used.

    Change

    Triple-quoted strings and sigils were introduced in Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Conversions │ │ │ │

    │ │ │ │ -

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ │ +

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ │  "hello"
    │ │ │ │ -2> list_to_atom("hello").
    │ │ │ │ +2> list_to_atom("hello").
    │ │ │ │  hello
    │ │ │ │ -3> binary_to_list(<<"hello">>).
    │ │ │ │ +3> binary_to_list(<<"hello">>).
    │ │ │ │  "hello"
    │ │ │ │ -4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ │ +4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ │  "hello"
    │ │ │ │ -5> list_to_binary("hello").
    │ │ │ │ -<<104,101,108,108,111>>
    │ │ │ │ -6> float_to_list(7.0).
    │ │ │ │ +5> list_to_binary("hello").
    │ │ │ │ +<<104,101,108,108,111>>
    │ │ │ │ +6> float_to_list(7.0).
    │ │ │ │  "7.00000000000000000000e+00"
    │ │ │ │ -7> list_to_float("7.000e+00").
    │ │ │ │ +7> list_to_float("7.000e+00").
    │ │ │ │  7.0
    │ │ │ │ -8> integer_to_list(77).
    │ │ │ │ +8> integer_to_list(77).
    │ │ │ │  "77"
    │ │ │ │ -9> list_to_integer("77").
    │ │ │ │ +9> list_to_integer("77").
    │ │ │ │  77
    │ │ │ │ -10> tuple_to_list({a,b,c}).
    │ │ │ │ -[a,b,c]
    │ │ │ │ -11> list_to_tuple([a,b,c]).
    │ │ │ │ -{a,b,c}
    │ │ │ │ -12> term_to_binary({a,b,c}).
    │ │ │ │ -<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ │ -13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ │ -{a,b,c}
    │ │ │ │ -14> binary_to_integer(<<"77">>).
    │ │ │ │ +10> tuple_to_list({a,b,c}).
    │ │ │ │ +[a,b,c]
    │ │ │ │ +11> list_to_tuple([a,b,c]).
    │ │ │ │ +{a,b,c}
    │ │ │ │ +12> term_to_binary({a,b,c}).
    │ │ │ │ +<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ │ +13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ │ +{a,b,c}
    │ │ │ │ +14> binary_to_integer(<<"77">>).
    │ │ │ │  77
    │ │ │ │ -15> integer_to_binary(77).
    │ │ │ │ -<<"77">>
    │ │ │ │ -16> float_to_binary(7.0).
    │ │ │ │ -<<"7.00000000000000000000e+00">>
    │ │ │ │ -17> binary_to_float(<<"7.000e+00">>).
    │ │ │ │ +15> integer_to_binary(77).
    │ │ │ │ +<<"77">>
    │ │ │ │ +16> float_to_binary(7.0).
    │ │ │ │ +<<"7.00000000000000000000e+00">>
    │ │ │ │ +17> binary_to_float(<<"7.000e+00">>).
    │ │ │ │  7.0
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/create_target.xhtml │ │ │ │ @@ -43,21 +43,21 @@ │ │ │ │ Creating a Target System │ │ │ │ │ │ │ │

    It is assumed that you have a working Erlang/OTP system structured according to │ │ │ │ the OTP design principles.

    Step 1. Create a .rel file (see the rel(4) manual page in │ │ │ │ SASL), which specifies the ERTS version and lists all applications that are to │ │ │ │ be included in the new basic target system. An example is the following │ │ │ │ mysystem.rel file:

    %% mysystem.rel
    │ │ │ │ -{release,
    │ │ │ │ - {"MYSYSTEM", "FIRST"},
    │ │ │ │ - {erts, "5.10.4"},
    │ │ │ │ - [{kernel, "2.16.4"},
    │ │ │ │ -  {stdlib, "1.19.4"},
    │ │ │ │ -  {sasl, "2.3.4"},
    │ │ │ │ -  {pea, "1.0"}]}.

    The listed applications are not only original Erlang/OTP applications but │ │ │ │ +{release, │ │ │ │ + {"MYSYSTEM", "FIRST"}, │ │ │ │ + {erts, "5.10.4"}, │ │ │ │ + [{kernel, "2.16.4"}, │ │ │ │ + {stdlib, "1.19.4"}, │ │ │ │ + {sasl, "2.3.4"}, │ │ │ │ + {pea, "1.0"}]}.

    The listed applications are not only original Erlang/OTP applications but │ │ │ │ possibly also new applications that you have written (here exemplified by the │ │ │ │ application Pea (pea)).

    Step 2. Start Erlang/OTP from the directory where the mysystem.rel file │ │ │ │ resides:

    % erl -pa /home/user/target_system/myapps/pea-1.0/ebin

    The -pa argument prepends the path to the ebin directory for │ │ │ │ the Pea application to the code path.

    Step 3. Create the target system:

    1> target_system:create("mysystem").

    The function target_system:create/1 performs the following:

    1. Reads the file mysystem.rel and creates a new file plain.rel. │ │ │ │ The new file is identical to the original, except that it only │ │ │ │ lists the Kernel and STDLIB applications.

    2. From the files mysystem.rel and plain.rel creates the files │ │ │ │ mysystem.script, mysystem.boot, plain.script, and plain.boot │ │ │ │ @@ -147,25 +147,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating the Next Version │ │ │ │ │ │ │ │

      In this example the Pea application has been changed, and so are the │ │ │ │ applications ERTS, Kernel, STDLIB and SASL.

      Step 1. Create the file .rel:

      %% mysystem2.rel
      │ │ │ │ -{release,
      │ │ │ │ - {"MYSYSTEM", "SECOND"},
      │ │ │ │ - {erts, "6.0"},
      │ │ │ │ - [{kernel, "3.0"},
      │ │ │ │ -  {stdlib, "2.0"},
      │ │ │ │ -  {sasl, "2.4"},
      │ │ │ │ -  {pea, "2.0"}]}.

      Step 2. Create the application upgrade file (see │ │ │ │ +{release, │ │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ │ + {erts, "6.0"}, │ │ │ │ + [{kernel, "3.0"}, │ │ │ │ + {stdlib, "2.0"}, │ │ │ │ + {sasl, "2.4"}, │ │ │ │ + {pea, "2.0"}]}.

    Step 2. Create the application upgrade file (see │ │ │ │ appup in SASL) for Pea, for example:

    %% pea.appup
    │ │ │ │ -{"2.0",
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ +{"2.0", │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

    % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

    Step 4. Create the release upgrade file (see relup │ │ │ │ in SASL):

    1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
    │ │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
    │ │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

    Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ │ upgrade to.

    The path option is used for pointing out the old version of all applications. │ │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ │ @@ -197,21 +197,21 @@ │ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ │ [End]

    The above return value and output after the call to │ │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ -  current},
    │ │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ -  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ +  current},
    │ │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ +  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ │ restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ │  [{"MYSYSTEM","SECOND",
    │ │ │ │    ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │    permanent},
    │ │ │ │   {"MYSYSTEM","FIRST",
    │ │ │ │    ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ @@ -220,264 +220,264 @@
    │ │ │ │    
    │ │ │ │      
    │ │ │ │    
    │ │ │ │    Listing of target_system.erl
    │ │ │ │  
    │ │ │ │  

    This module can also be found in the examples directory of the SASL │ │ │ │ application.

    
    │ │ │ │ --module(target_system).
    │ │ │ │ --export([create/1, create/2, install/2]).
    │ │ │ │ +-module(target_system).
    │ │ │ │ +-export([create/1, create/2, install/2]).
    │ │ │ │  
    │ │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
    │ │ │ │  %% .script etc.
    │ │ │ │  %%
    │ │ │ │  
    │ │ │ │  %% create(RelFileName)
    │ │ │ │  %%
    │ │ │ │ -create(RelFileName) ->
    │ │ │ │ -    create(RelFileName,[]).
    │ │ │ │ +create(RelFileName) ->
    │ │ │ │ +    create(RelFileName,[]).
    │ │ │ │  
    │ │ │ │ -create(RelFileName,SystoolsOpts) ->
    │ │ │ │ +create(RelFileName,SystoolsOpts) ->
    │ │ │ │      RelFile = RelFileName ++ ".rel",
    │ │ │ │ -    Dir = filename:dirname(RelFileName),
    │ │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │ +    Dir = filename:dirname(RelFileName),
    │ │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
    │ │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ -              [PlainRelFile, RelFile]),
    │ │ │ │ -    {release,
    │ │ │ │ -     {RelName, RelVsn},
    │ │ │ │ -     {erts, ErtsVsn},
    │ │ │ │ -     AppVsns} = RelSpec,
    │ │ │ │ -    PlainRelSpec = {release,
    │ │ │ │ -                    {RelName, RelVsn},
    │ │ │ │ -                    {erts, ErtsVsn},
    │ │ │ │ -                    lists:filter(fun({kernel, _}) ->
    │ │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ +              [PlainRelFile, RelFile]),
    │ │ │ │ +    {release,
    │ │ │ │ +     {RelName, RelVsn},
    │ │ │ │ +     {erts, ErtsVsn},
    │ │ │ │ +     AppVsns} = RelSpec,
    │ │ │ │ +    PlainRelSpec = {release,
    │ │ │ │ +                    {RelName, RelVsn},
    │ │ │ │ +                    {erts, ErtsVsn},
    │ │ │ │ +                    lists:filter(fun({kernel, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    ({stdlib, _}) ->
    │ │ │ │ +                                    ({stdlib, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    (_) ->
    │ │ │ │ +                                    (_) ->
    │ │ │ │                                           false
    │ │ │ │ -                                 end, AppVsns)
    │ │ │ │ -                   },
    │ │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ -    file:close(Fd),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -              [RelFileName, RelFileName]),
    │ │ │ │ -    make_script(RelFileName,SystoolsOpts),
    │ │ │ │ +                                 end, AppVsns)
    │ │ │ │ +                   },
    │ │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ +    file:close(Fd),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +              [RelFileName, RelFileName]),
    │ │ │ │ +    make_script(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │      TarFileName = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ -    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ +    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ -    file:make_dir(TmpDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ -    extract_tar(TarFileName, TmpDir),
    │ │ │ │ -
    │ │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir]),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ -    file:make_dir(TmpBinDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ +    file:make_dir(TmpDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ +    extract_tar(TarFileName, TmpDir),
    │ │ │ │ +
    │ │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ +              [ErtsBinDir]),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ +    file:make_dir(TmpBinDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │                "~ts to ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │ +              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │  
    │ │ │ │      %% This is needed if 'start' script created from 'start.src' shall
    │ │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
    │ │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ -    ok = file:make_dir(TmpLogDir),
    │ │ │ │ -
    │ │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ -    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ -	      [TarFileName,TmpDir]),
    │ │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ +    ok = file:make_dir(TmpLogDir),
    │ │ │ │ +
    │ │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ +    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ +	      [TarFileName,TmpDir]),
    │ │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │      %% {ok, Cwd} = file:get_cwd(),
    │ │ │ │      %% file:set_cwd("tmp"),
    │ │ │ │      ErtsDir = "erts-"++ErtsVsn,
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ -    erl_tar:close(Tar),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ +    erl_tar:close(Tar),
    │ │ │ │      %% file:set_cwd(Cwd),
    │ │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ -    remove_dir_tree(TmpDir),
    │ │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ +    remove_dir_tree(TmpDir),
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │ -install(RelFileName, RootDir) ->
    │ │ │ │ +install(RelFileName, RootDir) ->
    │ │ │ │      TarFile = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ -    extract_tar(TarFile, RootDir),
    │ │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ -              "form erl, start and start_erl ...\n"),
    │ │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ -                      [preserve]),
    │ │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ +    extract_tar(TarFile, RootDir),
    │ │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ +              "form erl, start and start_erl ...\n"),
    │ │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ +                      [preserve]),
    │ │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
    │ │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ -					    filename:basename(RelFileName)])).
    │ │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ +					    filename:basename(RelFileName)])).
    │ │ │ │  
    │ │ │ │  %% LOCALS
    │ │ │ │  
    │ │ │ │  %% make_script(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_script(RelFileName,Opts) ->
    │ │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ -				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				       |Opts]).
    │ │ │ │ +make_script(RelFileName,Opts) ->
    │ │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ +				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				       |Opts]).
    │ │ │ │  
    │ │ │ │  %% make_tar(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_tar(RelFileName,Opts) ->
    │ │ │ │ -    RootDir = code:root_dir(),
    │ │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ -				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				    |Opts]).
    │ │ │ │ +make_tar(RelFileName,Opts) ->
    │ │ │ │ +    RootDir = code:root_dir(),
    │ │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ +				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				    |Opts]).
    │ │ │ │  
    │ │ │ │  %% extract_tar(TarFile, DestDir)
    │ │ │ │  %%
    │ │ │ │ -extract_tar(TarFile, DestDir) ->
    │ │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │ +extract_tar(TarFile, DestDir) ->
    │ │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │  
    │ │ │ │ -create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │ +create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │  
    │ │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    lists:foreach(fun(Script) ->
    │ │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ -                                           Vars, Opts)
    │ │ │ │ -                  end, Scripts).
    │ │ │ │ -
    │ │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ -               filename:join([DestDir, Script]),
    │ │ │ │ -               Vars, Opts).
    │ │ │ │ -
    │ │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ -    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ -    NConts = subst(Conts, Vars),
    │ │ │ │ -    write_file(Dest, NConts),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    lists:foreach(fun(Script) ->
    │ │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ +                                           Vars, Opts)
    │ │ │ │ +                  end, Scripts).
    │ │ │ │ +
    │ │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ +               filename:join([DestDir, Script]),
    │ │ │ │ +               Vars, Opts).
    │ │ │ │ +
    │ │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ +    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ +    NConts = subst(Conts, Vars),
    │ │ │ │ +    write_file(Dest, NConts),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %% subst(Str, Vars)
    │ │ │ │  %% Vars = [{Var, Val}]
    │ │ │ │  %% Var = Val = string()
    │ │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
    │ │ │ │  %% of variables in Vars.
    │ │ │ │  %%
    │ │ │ │ -subst(Str, Vars) ->
    │ │ │ │ -    subst(Str, Vars, []).
    │ │ │ │ +subst(Str, Vars) ->
    │ │ │ │ +    subst(Str, Vars, []).
    │ │ │ │  
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([C| Rest], Vars, Result) ->
    │ │ │ │ -    subst(Rest, Vars, [C| Result]);
    │ │ │ │ -subst([], _Vars, Result) ->
    │ │ │ │ -    lists:reverse(Result).
    │ │ │ │ -
    │ │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    Key = lists:reverse(VarAcc),
    │ │ │ │ -    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ -        {value, {Key, Value}} ->
    │ │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([C| Rest], Vars, Result) ->
    │ │ │ │ +    subst(Rest, Vars, [C| Result]);
    │ │ │ │ +subst([], _Vars, Result) ->
    │ │ │ │ +    lists:reverse(Result).
    │ │ │ │ +
    │ │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    Key = lists:reverse(VarAcc),
    │ │ │ │ +    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ +        {value, {Key, Value}} ->
    │ │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │          false ->
    │ │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │      end;
    │ │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ -subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest) ->
    │ │ │ │ -    copy_file(Src, Dest, []).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest, Opts) ->
    │ │ │ │ -    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ +subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest) ->
    │ │ │ │ +    copy_file(Src, Dest, []).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest, Opts) ->
    │ │ │ │ +    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -write_file(FName, Conts) ->
    │ │ │ │ -    Enc = file:native_name_encoding(),
    │ │ │ │ -    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ -    file:close(Fd).
    │ │ │ │ -
    │ │ │ │ -read_txt_file(File) ->
    │ │ │ │ -    {ok, Bin} = file:read_file(File),
    │ │ │ │ -    {ok, binary_to_list(Bin)}.
    │ │ │ │ -
    │ │ │ │ -remove_dir_tree(Dir) ->
    │ │ │ │ -    remove_all_files(".", [Dir]).
    │ │ │ │ -
    │ │ │ │ -remove_all_files(Dir, Files) ->
    │ │ │ │ -    lists:foreach(fun(File) ->
    │ │ │ │ -                          FilePath = filename:join([Dir, File]),
    │ │ │ │ -                          case filelib:is_dir(FilePath) of
    │ │ │ │ +write_file(FName, Conts) ->
    │ │ │ │ +    Enc = file:native_name_encoding(),
    │ │ │ │ +    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ +    file:close(Fd).
    │ │ │ │ +
    │ │ │ │ +read_txt_file(File) ->
    │ │ │ │ +    {ok, Bin} = file:read_file(File),
    │ │ │ │ +    {ok, binary_to_list(Bin)}.
    │ │ │ │ +
    │ │ │ │ +remove_dir_tree(Dir) ->
    │ │ │ │ +    remove_all_files(".", [Dir]).
    │ │ │ │ +
    │ │ │ │ +remove_all_files(Dir, Files) ->
    │ │ │ │ +    lists:foreach(fun(File) ->
    │ │ │ │ +                          FilePath = filename:join([Dir, File]),
    │ │ │ │ +                          case filelib:is_dir(FilePath) of
    │ │ │ │                                true ->
    │ │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ -                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ -                                  file:del_dir(FilePath);
    │ │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ +                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ +                                  file:del_dir(FilePath);
    │ │ │ │                                _ ->
    │ │ │ │ -                                  file:delete(FilePath)
    │ │ │ │ +                                  file:delete(FilePath)
    │ │ │ │                            end
    │ │ │ │ -                  end, Files).
    │ │ │ │ + end, Files).
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/content.opf │ │ │ │ ├── OEBPS/content.opf │ │ │ │ │ @@ -1,14 +1,14 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Erlang System Documentation - 27.3.4.4 │ │ │ │ │ - urn:uuid:9247ffe1-d5e2-0701-dd1e-8e8e9ce19238 │ │ │ │ │ + urn:uuid:54ec9429-fb38-c9e3-ee2a-115e001eb4ea │ │ │ │ │ en │ │ │ │ │ - 2025-10-31T13:18:13Z │ │ │ │ │ + 2025-11-02T00:52:19Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -82,21 +82,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/conc_prog.xhtml │ │ │ │ @@ -37,107 +37,107 @@ │ │ │ │ threads of execution in an Erlang program and to allow these threads to │ │ │ │ communicate with each other. In Erlang, each thread of execution is called a │ │ │ │ process.

    (Aside: the term "process" is usually used when the threads of execution share │ │ │ │ no data with each other and the term "thread" when they share data in some way. │ │ │ │ Threads of execution in Erlang share no data, that is why they are called │ │ │ │ processes).

    The Erlang BIF spawn is used to create a new process: │ │ │ │ spawn(Module, Exported_Function, List of Arguments). Consider the following │ │ │ │ -module:

    -module(tut14).
    │ │ │ │ +module:

    -module(tut14).
    │ │ │ │  
    │ │ │ │ --export([start/0, say_something/2]).
    │ │ │ │ +-export([start/0, say_something/2]).
    │ │ │ │  
    │ │ │ │ -say_something(What, 0) ->
    │ │ │ │ +say_something(What, 0) ->
    │ │ │ │      done;
    │ │ │ │ -say_something(What, Times) ->
    │ │ │ │ -    io:format("~p~n", [What]),
    │ │ │ │ -    say_something(What, Times - 1).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ -    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ -{ok,tut14}
    │ │ │ │ -6> tut14:say_something(hello, 3).
    │ │ │ │ +say_something(What, Times) ->
    │ │ │ │ +    io:format("~p~n", [What]),
    │ │ │ │ +    say_something(What, Times - 1).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ +    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ +{ok,tut14}
    │ │ │ │ +6> tut14:say_something(hello, 3).
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  done

    As shown, the function say_something writes its first argument the number of │ │ │ │ times specified by second argument. The function start starts two Erlang │ │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ │ -the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │ +the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  <0.63.0>
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  hello
    │ │ │ │  goodbye

    Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ │ -last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ +last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ │ example shows how to use pids.

    Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

    ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ │ to output these as strings

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │ │

    │ │ │ │

    In the following example two processes are created and they send messages to │ │ │ │ -each other a number of times.

    -module(tut15).
    │ │ │ │ +each other a number of times.

    -module(tut15).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/2, pong/0]).
    │ │ │ │ +-export([start/0, ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_PID) ->
    │ │ │ │ +ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_PID) ->
    │ │ │ │ -    Pong_PID ! {ping, self()},
    │ │ │ │ +ping(N, Pong_PID) ->
    │ │ │ │ +    Pong_PID ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_PID).
    │ │ │ │ +    ping(N - 1, Pong_PID).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ -{ok,tut15}
    │ │ │ │ -2> tut15: start().
    │ │ │ │ +start() ->
    │ │ │ │ +    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ +{ok,tut15}
    │ │ │ │ +2> tut15: start().
    │ │ │ │  <0.36.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ -"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │ +Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ +"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │      finished ->
    │ │ │ │ -        io:format("Pong finished~n", []);
    │ │ │ │ -    {ping, Ping_PID} ->
    │ │ │ │ -        io:format("Pong received ping~n", []),
    │ │ │ │ +        io:format("Pong finished~n", []);
    │ │ │ │ +    {ping, Ping_PID} ->
    │ │ │ │ +        io:format("Pong received ping~n", []),
    │ │ │ │          Ping_PID ! pong,
    │ │ │ │ -        pong()
    │ │ │ │ +        pong()
    │ │ │ │  end.

    The receive construct is used to allow processes to wait for messages from │ │ │ │ other processes. It has the following format:

    receive
    │ │ │ │     pattern1 ->
    │ │ │ │         actions1;
    │ │ │ │     pattern2 ->
    │ │ │ │         actions2;
    │ │ │ │     ....
    │ │ │ │ @@ -158,84 +158,84 @@
    │ │ │ │  queue (keeping the first message and any other messages in the queue). If the
    │ │ │ │  second message does not match, the third message is tried, and so on, until the
    │ │ │ │  end of the queue is reached. If the end of the queue is reached, the process
    │ │ │ │  blocks (stops execution) and waits until a new message is received and this
    │ │ │ │  procedure is repeated.

    The Erlang implementation is "clever" and minimizes the number of times each │ │ │ │ message is tested against the patterns in each receive.

    Now back to the ping pong example.

    "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ │ -terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ +terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ process "ping":

    Ping_PID ! pong

    Notice how the operator "!" is used to send messages. The syntax of "!" is:

    Pid ! Message

    That is, Message (any Erlang term) is sent to the process with identity Pid.

    After sending the message pong to the process "ping", "pong" calls the pong │ │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ │ -another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ +another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ │ Ping_PID in the receive previously explained.)

    "Ping" now waits for a reply from "pong":

    receive
    │ │ │ │      pong ->
    │ │ │ │ -        io:format("Ping received pong~n", [])
    │ │ │ │ +        io:format("Ping received pong~n", [])
    │ │ │ │  end,

    It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ │ -the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ -occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │ +the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ +occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID !  finished,
    │ │ │ │ -    io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ + io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ │ it has nothing left to do.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │ │

    │ │ │ │

    In the above example, "pong" was first created to be able to give the identity │ │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ │ processes which need to know each other's identities are started independently │ │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ │ -using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ -the "pong" process:

    -module(tut16).
    │ │ │ │ +using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ +the "pong" process:

    -module(tut16).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/1, pong/0]).
    │ │ │ │ +-export([start/0, ping/1, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0) ->
    │ │ │ │ +ping(0) ->
    │ │ │ │      pong ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N) ->
    │ │ │ │ -    pong ! {ping, self()},
    │ │ │ │ +ping(N) ->
    │ │ │ │ +    pong ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1).
    │ │ │ │ +    ping(N - 1).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ -    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ -{ok, tut16}
    │ │ │ │ -3> tut16:start().
    │ │ │ │ +start() ->
    │ │ │ │ +    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ +    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ +{ok, tut16}
    │ │ │ │ +3> tut16:start().
    │ │ │ │  <0.38.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ -process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ +Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ +process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │ │

    │ │ │ │

    Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ │ @@ -255,106 +255,106 @@ │ │ │ │ of the file. This is a requirement.

    When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ │ you must give it a name, for example:

    $ erl -sname my_name

    We will see more details of this later. If you want to experiment with │ │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ │ Erlang system running on a computer is called an Erlang node.

    (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ │ -in full.)

    Here is the ping pong example modified to run on two separate nodes:

    -module(tut17).
    │ │ │ │ +in full.)

    Here is the ping pong example modified to run on two separate nodes:

    -module(tut17).
    │ │ │ │  
    │ │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +ping(0, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! finished,
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │ +ping(N, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start_pong() ->
    │ │ │ │ -    register(pong, spawn(tut17, pong, [])).
    │ │ │ │ +start_pong() ->
    │ │ │ │ +    register(pong, spawn(tut17, pong, [])).
    │ │ │ │  
    │ │ │ │ -start_ping(Pong_Node) ->
    │ │ │ │ -    spawn(tut17, ping, [3, Pong_Node]).

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ +start_ping(Pong_Node) -> │ │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

    On kosken (on a Linux/UNIX system):

    kosken> erl -sname ping
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │  (ping@kosken)1>

    On gollum:

    gollum> erl -sname pong
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │ -(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │ +(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │  true

    And the "ping" process on kosken is started (from the code above you can see │ │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ │ -system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │ +system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │  <0.37.0>
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished

    As shown, the ping pong program has run. On the "pong" side:

    (pong@gollum)2> 
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong finished
    │ │ │ │ -(pong@gollum)2> 

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ +(pong@gollum)2>

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ │ -node the "ping" process is executes:

    {ping, Ping_PID} ->
    │ │ │ │ -    io:format("Pong received ping~n", []),
    │ │ │ │ +node the "ping" process is executes:

    {ping, Ping_PID} ->
    │ │ │ │ +    io:format("Pong received ping~n", []),
    │ │ │ │      Ping_PID ! pong,

    Thus, Erlang pids contain information about where the process executes. So if │ │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ │ -message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ +message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ registered_name.

    In the previous example, "ping" and "pong" were started from the shells of two │ │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ │ nodes.

    The next example is the ping pong program, yet again, but this time "ping" is │ │ │ │ -started in another node:

    -module(tut18).
    │ │ │ │ +started in another node:

    -module(tut18).
    │ │ │ │  
    │ │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +ping(0, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! finished,
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │ +ping(N, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start(Ping_Node) ->
    │ │ │ │ -    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ -been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │ +start(Ping_Node) ->
    │ │ │ │ +    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ +been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │  <3934.39.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │ @@ -421,184 +421,184 @@
    │ │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │ │  %%% To client: logoff
    │ │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │ │  %%%
    │ │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │ │  %%% name of the node where the messenger server runs
    │ │ │ │  
    │ │ │ │ --module(messenger).
    │ │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │ +-module(messenger).
    │ │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │  
    │ │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │ │  %%% messenger server runs
    │ │ │ │ -server_node() ->
    │ │ │ │ +server_node() ->
    │ │ │ │      messenger@super.
    │ │ │ │  
    │ │ │ │  %%% This is the server process for the "messenger"
    │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ │ -server(User_List) ->
    │ │ │ │ +server(User_List) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, logon, Name} ->
    │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, logoff} ->
    │ │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ -            server(User_List)
    │ │ │ │ +        {From, logon, Name} ->
    │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, logoff} ->
    │ │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ +            server(User_List)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Start the server
    │ │ │ │ -start_server() ->
    │ │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │ +start_server() ->
    │ │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server adds a new user to the user list
    │ │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │ │      %% check if logged on anywhere else
    │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │ │          true ->
    │ │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │              User_List;
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, logged_on},
    │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ │ +            From ! {messenger, logged_on},
    │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Server deletes a user from the user list
    │ │ │ │ -server_logoff(From, User_List) ->
    │ │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ │ +server_logoff(From, User_List) ->
    │ │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server transfers a message between user
    │ │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │ │      %% check that the user is logged on and who he is
    │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ -        {value, {From, Name}} ->
    │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ +        {value, {From, Name}} ->
    │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │      end.
    │ │ │ │  %%% If the user exists, send the message
    │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │      %% Find the receiver and send the message
    │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ │ -            From ! {messenger, sent}
    │ │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ │ +            From ! {messenger, sent}
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% User Commands
    │ │ │ │ -logon(Name) ->
    │ │ │ │ -    case whereis(mess_client) of
    │ │ │ │ +logon(Name) ->
    │ │ │ │ +    case whereis(mess_client) of
    │ │ │ │          undefined ->
    │ │ │ │ -            register(mess_client,
    │ │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │ +            register(mess_client,
    │ │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │          _ -> already_logged_on
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -logoff() ->
    │ │ │ │ +logoff() ->
    │ │ │ │      mess_client ! logoff.
    │ │ │ │  
    │ │ │ │ -message(ToName, Message) ->
    │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ │ +message(ToName, Message) ->
    │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │ │          undefined ->
    │ │ │ │              not_logged_on;
    │ │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │               ok
    │ │ │ │  end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% The client process which runs on each server node
    │ │ │ │ -client(Server_Node, Name) ->
    │ │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ -    await_result(),
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +client(Server_Node, Name) ->
    │ │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ +    await_result(),
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │ -client(Server_Node) ->
    │ │ │ │ +client(Server_Node) ->
    │ │ │ │      receive
    │ │ │ │          logoff ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ -            await_result();
    │ │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ +            await_result();
    │ │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │      end,
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │  %%% wait for a response from the server
    │ │ │ │ -await_result() ->
    │ │ │ │ +await_result() ->
    │ │ │ │      receive
    │ │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ │ -            io:format("~p~n", [What])
    │ │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ │ +            io:format("~p~n", [What])
    │ │ │ │      end.

    To use this program, you need to:

    • Configure the server_node() function.
    • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ │ where you start Erlang.

    In the following example using this program, nodes are started on four different │ │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ │ can start several nodes on the same machine.

    Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ │ -c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ -true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │ +c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ +true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │  true
    │ │ │ │ -logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │ +logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │  true
    │ │ │ │ -logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │ +logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │  true
    │ │ │ │ -logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │ +logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │  ok
    │ │ │ │  sent

    Fred receives the message and sends a message to Peter and logs off:

    Message from peter: "hello"
    │ │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │  ok
    │ │ │ │  sent
    │ │ │ │ -(c3@gollum)3> messenger:logoff().
    │ │ │ │ -logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │ +(c3@gollum)3> messenger:logoff().
    │ │ │ │ +logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │  ok
    │ │ │ │  receiver_not_found

    But this fails as Fred has already logged off.

    First let us look at some of the new concepts that have been introduced.

    There are two versions of the server_transfer function: one with four │ │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ │ are regarded by Erlang as two separate functions.

    Notice how to write the server function so that it calls itself, through │ │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ │ function call. But this only works if there is no code after the call. │ │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ │ call. This would result in the process getting bigger and bigger for every loop.

    Functions in the lists module are used. This is a very useful module and a │ │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ │ -as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  true
    │ │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  false

    lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ │ -any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ +any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ {value,Tuple_Found} or the atom false.

    There are many very useful functions in the lists module.

    An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ │ in the system.

    A process terminates when there is nothing more for it to do, that is, the last │ │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ │ process running out of functions to call.

    The BIF whereis(RegisteredName) checks if a registered process │ │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ │ returned. If it does not exist, the atom undefined is returned.

    You should by now be able to understand most of the code in the │ │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ │ -user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ -sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ +user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ +sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ │ -the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ +the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ │ -the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ +the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ │ -sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ +sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/commoncaveats.xhtml │ │ │ │ @@ -23,31 +23,31 @@ │ │ │ │

    This section lists a few constructs to watch out for.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Operator ++ │ │ │ │

    │ │ │ │

    The ++ operator copies its left-hand side operand. That is clearly │ │ │ │ -seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ -    [H|my_plus_plus(T, Tail)];
    │ │ │ │ -my_plus_plus([], Tail) ->
    │ │ │ │ -    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ -    naive_reverse(T) ++ [H];
    │ │ │ │ -naive_reverse([]) ->
    │ │ │ │ -    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ -result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ -    naive_but_ok_reverse(List, []).
    │ │ │ │ +seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ +    [H|my_plus_plus(T, Tail)];
    │ │ │ │ +my_plus_plus([], Tail) ->
    │ │ │ │ +    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ +    naive_reverse(T) ++ [H];
    │ │ │ │ +naive_reverse([]) ->
    │ │ │ │ +    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ +result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ +    naive_but_ok_reverse(List, []).
    │ │ │ │  
    │ │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ -naive_but_ok_reverse([], Acc) ->
    │ │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ +naive_but_ok_reverse([], Acc) ->
    │ │ │ │      Acc.

    Each list element is copied only once. The growing result Acc is the right-hand │ │ │ │ -side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ -    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ -vanilla_reverse([], Acc) ->
    │ │ │ │ +side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ +    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ +vanilla_reverse([], Acc) ->
    │ │ │ │      Acc.

    In principle, this is slightly more efficient because the list element [H] │ │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ │ rewrites [H] ++ Acc to [H|Acc].

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ │ @@ -65,77 +65,77 @@ │ │ │ │ therefore harmless.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accidental Copying and Loss of Sharing │ │ │ │

    │ │ │ │

    When spawning a new process using a fun, one can accidentally copy more data to │ │ │ │ -the process than intended. For example:

    DO NOT

    accidental1(State) ->
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [State#state.info])
    │ │ │ │ -          end).

    The code in the fun will extract one element from the record and print it. The │ │ │ │ +the process than intended. For example:

    DO NOT

    accidental1(State) ->
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [State#state.info])
    │ │ │ │ +          end).

    The code in the fun will extract one element from the record and print it. The │ │ │ │ rest of the state record is not used. However, when the spawn/1 │ │ │ │ -function is executed, the entire record is copied to the newly created process.

    The same kind of problem can happen with a map:

    DO NOT

    accidental2(State) ->
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [map_get(info, State)])
    │ │ │ │ -          end).

    In the following example (part of a module implementing the gen_server │ │ │ │ -behavior) the created fun is sent to another process:

    DO NOT

    handle_call(give_me_a_fun, _From, State) ->
    │ │ │ │ -    Fun = fun() -> State#state.size =:= 42 end,
    │ │ │ │ -    {reply, Fun, State}.

    How bad that unnecessary copy is depends on the contents of the record or the │ │ │ │ -map.

    For example, if the state record is initialized like this:

    init1() ->
    │ │ │ │ -    #state{data=lists:seq(1, 10000)}.

    a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ │ +function is executed, the entire record is copied to the newly created process.

    The same kind of problem can happen with a map:

    DO NOT

    accidental2(State) ->
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [map_get(info, State)])
    │ │ │ │ +          end).

    In the following example (part of a module implementing the gen_server │ │ │ │ +behavior) the created fun is sent to another process:

    DO NOT

    handle_call(give_me_a_fun, _From, State) ->
    │ │ │ │ +    Fun = fun() -> State#state.size =:= 42 end,
    │ │ │ │ +    {reply, Fun, State}.

    How bad that unnecessary copy is depends on the contents of the record or the │ │ │ │ +map.

    For example, if the state record is initialized like this:

    init1() ->
    │ │ │ │ +    #state{data=lists:seq(1, 10000)}.

    a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ │ newly created process.

    An unnecessary copy of 10000 element list can be bad enough, but it can get even │ │ │ │ worse if the state record contains shared subterms. Here is a simple example │ │ │ │ -of a term with a shared subterm:

    {SubTerm, SubTerm}

    When a term is copied to another process, sharing of subterms will be lost and │ │ │ │ -the copied term can be many times larger than the original term. For example:

    init2() ->
    │ │ │ │ -    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
    │ │ │ │ -    #state{data=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ +of a term with a shared subterm:

    {SubTerm, SubTerm}

    When a term is copied to another process, sharing of subterms will be lost and │ │ │ │ +the copied term can be many times larger than the original term. For example:

    init2() ->
    │ │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
    │ │ │ │ +    #state{data=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ │ 131070 heap words. More details about │ │ │ │ loss off sharing are found in a later │ │ │ │ section.

    To avoid the problem, outside of the fun extract only the fields of the record │ │ │ │ -that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │ +that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │      Info = State#state.info,
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ -used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ -    Info = map_get(info, State),
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    │ │ │ │ + spawn(fun() -> │ │ │ │ + io:format("~p\n", [Info]) │ │ │ │ + end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ +used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ +    Info = map_get(info, State),
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [Info])
    │ │ │ │ +          end).

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1 │ │ │ │

    │ │ │ │

    Atoms are not garbage-collected. Once an atom is created, it is never removed. │ │ │ │ The emulator terminates if the limit for the number of atoms (1,048,576 by │ │ │ │ default) is reached.

    Therefore, converting arbitrary input strings to atoms can be dangerous in a │ │ │ │ system that runs continuously. If only certain well-defined atoms are allowed as │ │ │ │ input, list_to_existing_atom/1 or │ │ │ │ binary_to_existing_atom/1 can be used │ │ │ │ to guard against a denial-of-service attack. (All atoms that are allowed must │ │ │ │ have been created earlier, for example, by using all of them in a module │ │ │ │ and loading that module.)

    Using list_to_atom/1 to construct an atom that │ │ │ │ -is passed to apply/3 is quite expensive.

    DO NOT

    apply(list_to_atom("some_prefix"++Var), foo, Args)

    │ │ │ │ +is passed to apply/3 is quite expensive.

    DO NOT

    apply(list_to_atom("some_prefix"++Var), foo, Args)

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ length/1 │ │ │ │

    │ │ │ │

    The time for calculating the length of a list is proportional to the length of │ │ │ │ the list, as opposed to tuple_size/1, │ │ │ │ byte_size/1, and bit_size/1, which all │ │ │ │ execute in constant time.

    Normally, there is no need to worry about the speed of length/1, │ │ │ │ because it is efficiently implemented in C. In time-critical code, you might │ │ │ │ want to avoid it if the input list could potentially be very long.

    Some uses of length/1 can be replaced by matching. For example, │ │ │ │ -the following code:

    foo(L) when length(L) >= 3 ->
    │ │ │ │ -    ...

    can be rewritten to:

    foo([_,_,_|_]=L) ->
    │ │ │ │ +the following code:

    foo(L) when length(L) >= 3 ->
    │ │ │ │ +    ...

    can be rewritten to:

    foo([_,_,_|_]=L) ->
    │ │ │ │     ...

    One slight difference is that length(L) fails if L is an │ │ │ │ improper list, while the pattern in the second code fragment accepts an improper │ │ │ │ list.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ setelement/3 │ │ │ │ @@ -143,18 +143,18 @@ │ │ │ │

    setelement/3 copies the tuple it modifies. Therefore, │ │ │ │ updating a tuple in a loop using setelement/3 creates a new │ │ │ │ copy of the tuple every time.

    There is one exception to the rule that the tuple is copied. If the compiler │ │ │ │ clearly can see that destructively updating the tuple would give the same result │ │ │ │ as if the tuple was copied, the call to setelement/3 is │ │ │ │ replaced with a special destructive setelement instruction. In the following │ │ │ │ code sequence, the first setelement/3 call copies the tuple │ │ │ │ -and modifies the ninth element:

    multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
    │ │ │ │ -    T1 = setelement(9, T0, bar),
    │ │ │ │ -    T2 = setelement(7, T1, foobar),
    │ │ │ │ -    setelement(5, T2, new_value).

    The two following setelement/3 calls modify the tuple in │ │ │ │ +and modifies the ninth element:

    multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
    │ │ │ │ +    T1 = setelement(9, T0, bar),
    │ │ │ │ +    T2 = setelement(7, T1, foobar),
    │ │ │ │ +    setelement(5, T2, new_value).

    The two following setelement/3 calls modify the tuple in │ │ │ │ place.

    For the optimization to be applied, all the following conditions must be true:

    • The tuple argument must be known to be a tuple of a known size.
    • The indices must be integer literals, not variables or expressions.
    • The indices must be given in descending order.
    • There must be no calls to another function in between the calls to │ │ │ │ setelement/3.
    • The tuple returned from one setelement/3 call must only be │ │ │ │ used in the subsequent call to setelement/3.

    If the code cannot be structured as in the multiple_setelement/1 example, the │ │ │ │ best way to modify multiple elements in a large tuple is to convert the tuple to │ │ │ │ a list, modify the list, and convert it back to a tuple.

    │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/code_loading.xhtml │ │ │ │ @@ -27,16 +27,16 @@ │ │ │ │ │ │ │ │ │ │ │ │ Compilation │ │ │ │

    │ │ │ │

    Erlang programs must be compiled to object code. The compiler can generate a │ │ │ │ new file that contains the object code. The current abstract machine, which runs │ │ │ │ the object code, is called BEAM, therefore the object files get the suffix │ │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ -compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ +compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ loads Module.

    There is also a module make, which provides a set of functions similar to the │ │ │ │ UNIX type Make functions, see module make in Tools.

    The compiler can also be accessed from the OS prompt using the │ │ │ │ erl executable in ERTS.

    % erl -compile Module1...ModuleN
    │ │ │ │  % erl -make

    The erlc program provides way to compile modules from the OS │ │ │ │ shell, see the erlc executable in ERTS. It │ │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ │ paths for include files, and more.

    % erlc <flags> File1.erl...FileN.erl

    │ │ │ │ @@ -61,51 +61,51 @@ │ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

    Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ │ evaluated because of processes lingering in the old code.

    If a third instance of the module is loaded, the code server removes (purges) │ │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

    To change from old code to current code, a process must make a fully qualified │ │ │ │ -function call.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +function call.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          code_switch ->
    │ │ │ │ -            m:loop();
    │ │ │ │ +            m:loop();
    │ │ │ │          Msg ->
    │ │ │ │              ...
    │ │ │ │ -            loop()
    │ │ │ │ +            loop()
    │ │ │ │      end.

    To make the process change code, send the message code_switch to it. The │ │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ │ code. Notice that m:loop/0 must be exported.

    For code replacement of funs to work, use the syntax │ │ │ │ fun Module:FunctionName/Arity.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │ │

    │ │ │ │

    The -on_load() directive names a function that is to be run automatically when │ │ │ │ -a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ +a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ process (which terminates as soon as the function returns).

    The function must return ok if the module is to become the new current code │ │ │ │ for the module and become callable.

    Returning any other value or generating an exception causes the new code to be │ │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ │ the error logger.

    If there already is current code for the module, that code will remain current │ │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ │ current code for a module, any process that makes an external call to the module │ │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ │ function have finished.

    Change

    Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ │ code would become old, essentially leaving the system without any working and │ │ │ │ reachable instance of the module.

    In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ │ -ok.

    Example:

    -module(m).
    │ │ │ │ --on_load(load_my_nifs/0).
    │ │ │ │ +ok.

    Example:

    -module(m).
    │ │ │ │ +-on_load(load_my_nifs/0).
    │ │ │ │  
    │ │ │ │ -load_my_nifs() ->
    │ │ │ │ +load_my_nifs() ->
    │ │ │ │      NifPath = ...,    %Set up the path to the NIF library.
    │ │ │ │      Info = ...,       %Initialize the Info term
    │ │ │ │ -    erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ + erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ report is sent to the error loader.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/c_portdriver.xhtml │ │ │ │ @@ -56,112 +56,112 @@ │ │ │ │

    Like a port program, the port communicates with an Erlang process. All │ │ │ │ communication goes through one Erlang process that is the connected process of │ │ │ │ the port driver. Terminating this process closes the port driver.

    Before the port is created, the driver must be loaded. This is done with the │ │ │ │ function erl_ddll:load_driver/2, with the name of the shared library as │ │ │ │ argument.

    The port is then created using the BIF open_port/2, with the │ │ │ │ tuple {spawn, DriverName} as the first argument. The string SharedLib is the │ │ │ │ name of the port driver. The second argument is a list of options, none in this │ │ │ │ -case:

    -module(complex5).
    │ │ │ │ --export([start/1, init/1]).
    │ │ │ │ +case:

    -module(complex5).
    │ │ │ │ +-export([start/1, init/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │          ok -> ok;
    │ │ │ │ -        {error, already_loaded} -> ok;
    │ │ │ │ -        _ -> exit({error, could_not_load_driver})
    │ │ │ │ +        {error, already_loaded} -> ok;
    │ │ │ │ +        _ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -  register(complex, self()),
    │ │ │ │ -  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ -message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +  register(complex, self()),
    │ │ │ │ +  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ +message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -        {complex, Result} ->
    │ │ │ │ +        {complex, Result} ->
    │ │ │ │              Result
    │ │ │ │ -    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │ +    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, Caller, Msg} ->
    │ │ │ │ -            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +        {call, Caller, Msg} ->
    │ │ │ │ +            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │              receive
    │ │ │ │ -                {Port, {data, Data}} ->
    │ │ │ │ -                    Caller ! {complex, decode(Data)}
    │ │ │ │ +                {Port, {data, Data}} ->
    │ │ │ │ +                    Caller ! {complex, decode(Data)}
    │ │ │ │              end,
    │ │ │ │ -            loop(Port)
    │ │ │ │ +            loop(Port)
    │ │ │ │      end.

    Assuming that both the arguments and the results from the C functions are less │ │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ │ -represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ +decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ detecting port failures, is as follows:

    
    │ │ │ │ --module(complex5).
    │ │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ │ --export([foo/1, bar/1]).
    │ │ │ │ +-module(complex5).
    │ │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ │ +-export([foo/1, bar/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │  	ok -> ok;
    │ │ │ │ -	{error, already_loaded} -> ok;
    │ │ │ │ -	_ -> exit({error, could_not_load_driver})
    │ │ │ │ +	{error, already_loaded} -> ok;
    │ │ │ │ +	_ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -    register(complex, self()),
    │ │ │ │ -    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -    loop(Port).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +    register(complex, self()),
    │ │ │ │ +    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +    loop(Port).
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ +stop() ->
    │ │ │ │      complex ! stop.
    │ │ │ │  
    │ │ │ │ -foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -	{complex, Result} ->
    │ │ │ │ +	{complex, Result} ->
    │ │ │ │  	    Result
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -loop(Port) ->
    │ │ │ │ +loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -	{call, Caller, Msg} ->
    │ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +	{call, Caller, Msg} ->
    │ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, {data, Data}} ->
    │ │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ │ +		{Port, {data, Data}} ->
    │ │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │ │  	    end,
    │ │ │ │ -	    loop(Port);
    │ │ │ │ +	    loop(Port);
    │ │ │ │  	stop ->
    │ │ │ │ -	    Port ! {self(), close},
    │ │ │ │ +	    Port ! {self(), close},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, closed} ->
    │ │ │ │ -		    exit(normal)
    │ │ │ │ +		{Port, closed} ->
    │ │ │ │ +		    exit(normal)
    │ │ │ │  	    end;
    │ │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ │ -	    io:format("~p ~n", [Reason]),
    │ │ │ │ -	    exit(port_terminated)
    │ │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ │ +	    io:format("~p ~n", [Reason]),
    │ │ │ │ +	    exit(port_terminated)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    │ │ │ │ +decode([Int]) -> Int.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ C Driver │ │ │ │

    │ │ │ │

    The C driver is a module that is compiled and linked into a shared library. It │ │ │ │ uses a driver structure and includes the header file erl_driver.h.

    The driver structure is filled with the driver name and function pointers. It is │ │ │ │ @@ -252,22 +252,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

    │ │ │ │

    Step 1. Compile the C code:

    unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
    │ │ │ │  windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c

    Step 2. Start Erlang and compile the Erlang code:

    > erl
    │ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ -1> c(complex5).
    │ │ │ │ -{ok,complex5}

    Step 3. Run the example:

    2> complex5:start("example_drv").
    │ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ +1> c(complex5).
    │ │ │ │ +{ok,complex5}

    Step 3. Run the example:

    2> complex5:start("example_drv").
    │ │ │ │  <0.34.0>
    │ │ │ │ -3> complex5:foo(3).
    │ │ │ │ +3> complex5:foo(3).
    │ │ │ │  4
    │ │ │ │ -4> complex5:bar(5).
    │ │ │ │ +4> complex5:bar(5).
    │ │ │ │  10
    │ │ │ │ -5> complex5:stop().
    │ │ │ │ +5> complex5:stop().
    │ │ │ │  stop
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/c_port.xhtml │ │ │ │ @@ -53,101 +53,101 @@ │ │ │ │ external program, if it is written properly).

    The port is created using the BIF open_port/2 with │ │ │ │ {spawn,ExtPrg} as the first argument. The string ExtPrg is the name of the │ │ │ │ external program, including any command line arguments. The second argument is a │ │ │ │ list of options, in this case only {packet,2}. This option says that a 2 byte │ │ │ │ length indicator is to be used to simplify the communication between C and │ │ │ │ Erlang. The Erlang port automatically adds the length indicator, but this must │ │ │ │ be done explicitly in the external C program.

    The process is also set to trap exits, which enables detection of failure of the │ │ │ │ -external program:

    -module(complex1).
    │ │ │ │ --export([start/1, init/1]).
    │ │ │ │ +external program:

    -module(complex1).
    │ │ │ │ +-export([start/1, init/1]).
    │ │ │ │  
    │ │ │ │ -start(ExtPrg) ->
    │ │ │ │ -  spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ +start(ExtPrg) ->
    │ │ │ │ +  spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │  
    │ │ │ │ -init(ExtPrg) ->
    │ │ │ │ -  register(complex, self()),
    │ │ │ │ -  process_flag(trap_exit, true),
    │ │ │ │ -  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ -  loop(Port).

    Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ │ -message to the complex process and receive the following replies:

    foo(X) ->
    │ │ │ │ -  call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -  call_port({bar, Y}).
    │ │ │ │ +init(ExtPrg) ->
    │ │ │ │ +  register(complex, self()),
    │ │ │ │ +  process_flag(trap_exit, true),
    │ │ │ │ +  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ +  loop(Port).

    Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ │ +message to the complex process and receive the following replies:

    foo(X) ->
    │ │ │ │ +  call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +  call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -  complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +  complex ! {call, self(), Msg},
    │ │ │ │    receive
    │ │ │ │ -    {complex, Result} ->
    │ │ │ │ +    {complex, Result} ->
    │ │ │ │        Result
    │ │ │ │ -  end.

    The complex process does the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │ +  end.

    The complex process does the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │    receive
    │ │ │ │ -    {call, Caller, Msg} ->
    │ │ │ │ -      Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +    {call, Caller, Msg} ->
    │ │ │ │ +      Port ! {self(), {command, encode(Msg)}},
    │ │ │ │        receive
    │ │ │ │ -        {Port, {data, Data}} ->
    │ │ │ │ -          Caller ! {complex, decode(Data)}
    │ │ │ │ +        {Port, {data, Data}} ->
    │ │ │ │ +          Caller ! {complex, decode(Data)}
    │ │ │ │        end,
    │ │ │ │ -      loop(Port)
    │ │ │ │ +      loop(Port)
    │ │ │ │    end.

    Assuming that both the arguments and the results from the C functions are less │ │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ │ -represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    The resulting Erlang program, including functionality for stopping the port and │ │ │ │ -detecting port failures, is as follows:

    -module(complex1).
    │ │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ │ --export([foo/1, bar/1]).
    │ │ │ │ -
    │ │ │ │ -start(ExtPrg) ->
    │ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ -stop() ->
    │ │ │ │ +decode([Int]) -> Int.

    The resulting Erlang program, including functionality for stopping the port and │ │ │ │ +detecting port failures, is as follows:

    -module(complex1).
    │ │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ │ +-export([foo/1, bar/1]).
    │ │ │ │ +
    │ │ │ │ +start(ExtPrg) ->
    │ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ +stop() ->
    │ │ │ │      complex ! stop.
    │ │ │ │  
    │ │ │ │ -foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -	{complex, Result} ->
    │ │ │ │ +	{complex, Result} ->
    │ │ │ │  	    Result
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -init(ExtPrg) ->
    │ │ │ │ -    register(complex, self()),
    │ │ │ │ -    process_flag(trap_exit, true),
    │ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ -    loop(Port).
    │ │ │ │ +init(ExtPrg) ->
    │ │ │ │ +    register(complex, self()),
    │ │ │ │ +    process_flag(trap_exit, true),
    │ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ +    loop(Port).
    │ │ │ │  
    │ │ │ │ -loop(Port) ->
    │ │ │ │ +loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -	{call, Caller, Msg} ->
    │ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +	{call, Caller, Msg} ->
    │ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, {data, Data}} ->
    │ │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ │ +		{Port, {data, Data}} ->
    │ │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │ │  	    end,
    │ │ │ │ -	    loop(Port);
    │ │ │ │ +	    loop(Port);
    │ │ │ │  	stop ->
    │ │ │ │ -	    Port ! {self(), close},
    │ │ │ │ +	    Port ! {self(), close},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, closed} ->
    │ │ │ │ -		    exit(normal)
    │ │ │ │ +		{Port, closed} ->
    │ │ │ │ +		    exit(normal)
    │ │ │ │  	    end;
    │ │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ │ -	    exit(port_terminated)
    │ │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ │ +	    exit(port_terminated)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    │ │ │ │ +decode([Int]) -> Int.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ C Program │ │ │ │

    │ │ │ │

    On the C side, it is necessary to write functions for receiving and sending data │ │ │ │ with 2 byte length indicators from/to Erlang. By default, the C program is to │ │ │ │ @@ -238,22 +238,22 @@ │ │ │ │ and terminates.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

    │ │ │ │

    Step 1. Compile the C code:

    $ gcc -o extprg complex.c erl_comm.c port.c

    Step 2. Start Erlang and compile the Erlang code:

    $ erl
    │ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ -1> c(complex1).
    │ │ │ │ -{ok,complex1}

    Step 3. Run the example:

    2> complex1:start("./extprg").
    │ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ +1> c(complex1).
    │ │ │ │ +{ok,complex1}

    Step 3. Run the example:

    2> complex1:start("./extprg").
    │ │ │ │  <0.34.0>
    │ │ │ │ -3> complex1:foo(3).
    │ │ │ │ +3> complex1:foo(3).
    │ │ │ │  4
    │ │ │ │ -4> complex1:bar(5).
    │ │ │ │ +4> complex1:bar(5).
    │ │ │ │  10
    │ │ │ │ -5> complex1:stop().
    │ │ │ │ +5> complex1:stop().
    │ │ │ │  stop
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/bit_syntax.xhtml │ │ │ │ @@ -24,48 +24,48 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Introduction │ │ │ │ │ │ │ │

    The complete specification for the bit syntax appears in the │ │ │ │ Reference Manual.

    In Erlang, a Bin is used for constructing binaries and matching binary patterns. │ │ │ │ -A Bin is written with the following syntax:

    <<E1, E2, ... En>>

    A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ │ -enable construction of binaries:

    Bin = <<E1, E2, ... En>>

    All elements must be bound. Or match a binary:

    <<E1, E2, ... En>> = Bin

    Here, Bin is bound and the elements are bound or unbound, as in any match.

    A Bin does not need to consist of a whole number of bytes.

    A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ │ +A Bin is written with the following syntax:

    <<E1, E2, ... En>>

    A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ │ +enable construction of binaries:

    Bin = <<E1, E2, ... En>>

    All elements must be bound. Or match a binary:

    <<E1, E2, ... En>> = Bin

    Here, Bin is bound and the elements are bound or unbound, as in any match.

    A Bin does not need to consist of a whole number of bytes.

    A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ │ not need to be divisible by 8. If the number of bits is divisible by 8, the │ │ │ │ bitstring is also a binary.

    Each element specifies a certain segment of the bitstring. A segment is a set │ │ │ │ of contiguous bits of the binary (not necessarily on a byte boundary). The first │ │ │ │ element specifies the initial segment, the second element specifies the │ │ │ │ following segment, and so on.

    The following examples illustrate how binaries are constructed, or matched, and │ │ │ │ how elements and tails are specified.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │ │

    │ │ │ │

    Example 1: A binary can be constructed from a set of constants or a string │ │ │ │ -literal:

    Bin11 = <<1, 17, 42>>,
    │ │ │ │ -Bin12 = <<"abc">>

    This gives two binaries of size 3, with the following evaluations:

    Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ │ +literal:

    Bin11 = <<1, 17, 42>>,
    │ │ │ │ +Bin12 = <<"abc">>

    This gives two binaries of size 3, with the following evaluations:

    Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ │ variables:

    A = 1, B = 17, C = 42,
    │ │ │ │ -Bin2 = <<A, B, C:16>>

    This gives a binary of size 4. Here, a size expression is used for the │ │ │ │ +Bin2 = <<A, B, C:16>>

    This gives a binary of size 4. Here, a size expression is used for the │ │ │ │ variable C to specify a 16-bits segment of Bin2.

    binary_to_list(Bin2) evaluates to [1, 17, 00, 42].

    Example 3: A Bin can also be used for matching. D, E, and F are unbound │ │ │ │ -variables, and Bin2 is bound, as in Example 2:

    <<D:16, E, F/binary>> = Bin2

    This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ │ +variables, and Bin2 is bound, as in Example 2:

    <<D:16, E, F/binary>> = Bin2

    This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ │ binary_to_list(F) = [42].

    Example 4: The following is a more elaborate example of matching. Here, │ │ │ │ Dgram is bound to the consecutive bytes of an IP datagram of IP protocol │ │ │ │ -version 4. The ambition is to extract the header and the data of the datagram:

    -define(IP_VERSION, 4).
    │ │ │ │ --define(IP_MIN_HDR_LEN, 5).
    │ │ │ │ +version 4. The ambition is to extract the header and the data of the datagram:

    -define(IP_VERSION, 4).
    │ │ │ │ +-define(IP_MIN_HDR_LEN, 5).
    │ │ │ │  
    │ │ │ │ -DgramSize = byte_size(Dgram),
    │ │ │ │ +DgramSize = byte_size(Dgram),
    │ │ │ │  case Dgram of
    │ │ │ │ -    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
    │ │ │ │ +    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
    │ │ │ │        ID:16, Flgs:3, FragOff:13,
    │ │ │ │        TTL:8, Proto:8, HdrChkSum:16,
    │ │ │ │        SrcIP:32,
    │ │ │ │ -      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
    │ │ │ │ -        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
    │ │ │ │ -        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
    │ │ │ │ +      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
    │ │ │ │ +        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
    │ │ │ │ +        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
    │ │ │ │      ...
    │ │ │ │  end.

    Here, the segment corresponding to the Opts variable has a type modifier, │ │ │ │ specifying that Opts is to bind to a binary. All other variables have the │ │ │ │ default type equal to unsigned integer.

    An IP datagram header is of variable length. This length is measured in the │ │ │ │ number of 32-bit words and is given in the segment corresponding to HLen. The │ │ │ │ minimum value of HLen is 5. It is the segment corresponding to Opts that is │ │ │ │ variable, so if HLen is equal to 5, Opts becomes an empty binary.

    The tail variables RestDgram and Data bind to binaries, as all tail │ │ │ │ @@ -123,77 +123,77 @@ │ │ │ │

    This section describes the rules for constructing binaries using the bit syntax. │ │ │ │ Unlike when constructing lists or tuples, the construction of a binary can fail │ │ │ │ with a badarg exception.

    There can be zero or more segments in a binary to be constructed. The expression │ │ │ │ <<>> constructs a zero length binary.

    Each segment in a binary can consist of zero or more bits. There are no │ │ │ │ alignment rules for individual segments of type integer and float. For │ │ │ │ binaries and bitstrings without size, the unit specifies the alignment. Since │ │ │ │ the default alignment for the binary type is 8, the size of a binary segment │ │ │ │ -must be a multiple of 8 bits, that is, only whole bytes.

    Example:

    <<Bin/binary,Bitstring/bitstring>>

    The variable Bin must contain a whole number of bytes, because the binary │ │ │ │ +must be a multiple of 8 bits, that is, only whole bytes.

    Example:

    <<Bin/binary,Bitstring/bitstring>>

    The variable Bin must contain a whole number of bytes, because the binary │ │ │ │ type defaults to unit:8. A badarg exception is generated if Bin consist │ │ │ │ of, for example, 17 bits.

    The Bitstring variable can consist of any number of bits, for example, 0, 1, │ │ │ │ 8, 11, 17, 42, and so on. This is because the default unit for bitstrings │ │ │ │ is 1.

    For clarity, it is recommended not to change the unit size for binaries. │ │ │ │ Instead, use binary when you need byte alignment and bitstring when you need │ │ │ │ bit alignment.

    The following example successfully constructs a bitstring of 7 bits, provided │ │ │ │ -that all of X and Y are integers:

    <<X:1,Y:6>>

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When constructing binaries, Value and Size can be any Erlang expression. │ │ │ │ +that all of X and Y are integers:

    <<X:1,Y:6>>

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When constructing binaries, Value and Size can be any Erlang expression. │ │ │ │ However, for syntactical reasons, both Value and Size must be enclosed in │ │ │ │ parenthesis if the expression consists of anything more than a single literal or │ │ │ │ -a variable. The following gives a compiler syntax error:

    <<X+1:8>>

    This expression must be rewritten into the following, to be accepted by the │ │ │ │ -compiler:

    <<(X+1):8>>

    │ │ │ │ +a variable. The following gives a compiler syntax error:

    <<X+1:8>>

    This expression must be rewritten into the following, to be accepted by the │ │ │ │ +compiler:

    <<(X+1):8>>

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Including Literal Strings │ │ │ │

    │ │ │ │ -

    A literal string can be written instead of an element:

    <<"hello">>

    This is syntactic sugar for the following:

    <<$h,$e,$l,$l,$o>>

    │ │ │ │ +

    A literal string can be written instead of an element:

    <<"hello">>

    This is syntactic sugar for the following:

    <<$h,$e,$l,$l,$o>>

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │ │

    │ │ │ │

    This section describes the rules for matching binaries, using the bit syntax.

    There can be zero or more segments in a binary pattern. A binary pattern can │ │ │ │ occur wherever patterns are allowed, including inside other patterns. Binary │ │ │ │ patterns cannot be nested. The pattern <<>> matches a zero length binary.

    Each segment in a binary can consist of zero or more bits. A segment of type │ │ │ │ binary must have a size evenly divisible by 8 (or divisible by the unit size, │ │ │ │ if the unit size has been changed). A segment of type bitstring has no │ │ │ │ restrictions on the size. A segment of type float must have size 64 or 32.

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When matching Value, value must be either a variable or an integer, or a │ │ │ │ floating point literal. Expressions are not allowed.

    Size must be a │ │ │ │ guard expression, which can use │ │ │ │ -literals and previously bound variables. The following is not allowed:

    foo(N, <<X:N,T/binary>>) ->
    │ │ │ │ -   {X,T}.

    The two occurrences of N are not related. The compiler will complain that the │ │ │ │ -N in the size field is unbound.

    The correct way to write this example is as follows:

    foo(N, Bin) ->
    │ │ │ │ -   <<X:N,T/binary>> = Bin,
    │ │ │ │ -   {X,T}.

    Note

    Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ │ +literals and previously bound variables. The following is not allowed:

    foo(N, <<X:N,T/binary>>) ->
    │ │ │ │ +   {X,T}.

    The two occurrences of N are not related. The compiler will complain that the │ │ │ │ +N in the size field is unbound.

    The correct way to write this example is as follows:

    foo(N, Bin) ->
    │ │ │ │ +   <<X:N,T/binary>> = Bin,
    │ │ │ │ +   {X,T}.

    Note

    Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ │ an integer.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Binding and Using a Size Variable │ │ │ │

    │ │ │ │

    There is one exception to the rule that a variable that is used as size must be │ │ │ │ previously bound. It is possible to match and bind a variable, and use it as a │ │ │ │ -size within the same binary pattern. For example:

    bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
    │ │ │ │ -   {Payload,Rest}.

    Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ │ -used at the number of bytes to match out as a binary.

    Starting in OTP 23, the size can be a guard expression:

    bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
    │ │ │ │ -   {Payload,Rest}.

    Here Sz is the combined size of the header and the payload, so we will need to │ │ │ │ +size within the same binary pattern. For example:

    bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
    │ │ │ │ +   {Payload,Rest}.

    Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ │ +used at the number of bytes to match out as a binary.

    Starting in OTP 23, the size can be a guard expression:

    bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
    │ │ │ │ +   {Payload,Rest}.

    Here Sz is the combined size of the header and the payload, so we will need to │ │ │ │ subtract one byte to get the size of the payload.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Getting the Rest of the Binary or Bitstring │ │ │ │

    │ │ │ │ -

    To match out the rest of a binary, specify a binary field without size:

    foo(<<A:8,Rest/binary>>) ->

    The size of the tail must be evenly divisible by 8.

    To match out the rest of a bitstring, specify a field without size:

    foo(<<A:8,Rest/bitstring>>) ->

    There are no restrictions on the number of bits in the tail.

    │ │ │ │ +

    To match out the rest of a binary, specify a binary field without size:

    foo(<<A:8,Rest/binary>>) ->

    The size of the tail must be evenly divisible by 8.

    To match out the rest of a bitstring, specify a field without size:

    foo(<<A:8,Rest/bitstring>>) ->

    There are no restrictions on the number of bits in the tail.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Appending to a Binary │ │ │ │

    │ │ │ │ -

    Appending to a binary in an efficient way can be done as follows:

    triples_to_bin(T) ->
    │ │ │ │ -    triples_to_bin(T, <<>>).
    │ │ │ │ +

    Appending to a binary in an efficient way can be done as follows:

    triples_to_bin(T) ->
    │ │ │ │ +    triples_to_bin(T, <<>>).
    │ │ │ │  
    │ │ │ │ -triples_to_bin([{X,Y,Z} | T], Acc) ->
    │ │ │ │ -    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
    │ │ │ │ -triples_to_bin([], Acc) ->
    │ │ │ │ +triples_to_bin([{X,Y,Z} | T], Acc) ->
    │ │ │ │ +    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
    │ │ │ │ +triples_to_bin([], Acc) ->
    │ │ │ │      Acc.
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/binaryhandling.xhtml │ │ │ │ @@ -19,43 +19,43 @@ │ │ │ │ │ │ │ │

    │ │ │ │ Constructing and Matching Binaries │ │ │ │

    │ │ │ │

    This section gives a few examples on how to handle binaries in an efficient way. │ │ │ │ The sections that follow take an in-depth look at how binaries are implemented │ │ │ │ and how to best take advantages of the optimizations done by the compiler and │ │ │ │ -runtime system.

    Binaries can be efficiently built in the following way:

    DO

    my_list_to_binary(List) ->
    │ │ │ │ -    my_list_to_binary(List, <<>>).
    │ │ │ │ +runtime system.

    Binaries can be efficiently built in the following way:

    DO

    my_list_to_binary(List) ->
    │ │ │ │ +    my_list_to_binary(List, <<>>).
    │ │ │ │  
    │ │ │ │ -my_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    my_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ -my_list_to_binary([], Acc) ->
    │ │ │ │ +my_list_to_binary([H|T], Acc) ->
    │ │ │ │ +    my_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ +my_list_to_binary([], Acc) ->
    │ │ │ │      Acc.

    Appending data to a binary as in the example is efficient because it is │ │ │ │ specially optimized by the runtime system to avoid copying the Acc binary │ │ │ │ -every time.

    Prepending data to a binary in a loop is not efficient:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ -    rev_list_to_binary(List, <<>>).
    │ │ │ │ +every time.

    Prepending data to a binary in a loop is not efficient:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ +    rev_list_to_binary(List, <<>>).
    │ │ │ │  
    │ │ │ │ -rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    rev_list_to_binary(T, <<H,Acc/binary>>);
    │ │ │ │ -rev_list_to_binary([], Acc) ->
    │ │ │ │ +rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ +    rev_list_to_binary(T, <<H,Acc/binary>>);
    │ │ │ │ +rev_list_to_binary([], Acc) ->
    │ │ │ │      Acc.

    This is not efficient for long lists because the Acc binary is copied every │ │ │ │ -time. One way to make the function more efficient is like this:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ -    rev_list_to_binary(lists:reverse(List), <<>>).
    │ │ │ │ +time. One way to make the function more efficient is like this:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ +    rev_list_to_binary(lists:reverse(List), <<>>).
    │ │ │ │  
    │ │ │ │ -rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    rev_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ -rev_list_to_binary([], Acc) ->
    │ │ │ │ -    Acc.

    Another way to avoid copying the binary each time is like this:

    DO

    rev_list_to_binary([H|T]) ->
    │ │ │ │ -    RevTail = rev_list_to_binary(T),
    │ │ │ │ -    <<RevTail/binary,H>>;
    │ │ │ │ -rev_list_to_binary([]) ->
    │ │ │ │ -    <<>>.

    Note that in each of the DO examples, the binary to be appended to is always │ │ │ │ -given as the first segment.

    Binaries can be efficiently matched in the following way:

    DO

    my_binary_to_list(<<H,T/binary>>) ->
    │ │ │ │ -    [H|my_binary_to_list(T)];
    │ │ │ │ -my_binary_to_list(<<>>) -> [].

    │ │ │ │ +rev_list_to_binary([H|T], Acc) -> │ │ │ │ + rev_list_to_binary(T, <<Acc/binary,H>>); │ │ │ │ +rev_list_to_binary([], Acc) -> │ │ │ │ + Acc.

    Another way to avoid copying the binary each time is like this:

    DO

    rev_list_to_binary([H|T]) ->
    │ │ │ │ +    RevTail = rev_list_to_binary(T),
    │ │ │ │ +    <<RevTail/binary,H>>;
    │ │ │ │ +rev_list_to_binary([]) ->
    │ │ │ │ +    <<>>.

    Note that in each of the DO examples, the binary to be appended to is always │ │ │ │ +given as the first segment.

    Binaries can be efficiently matched in the following way:

    DO

    my_binary_to_list(<<H,T/binary>>) ->
    │ │ │ │ +    [H|my_binary_to_list(T)];
    │ │ │ │ +my_binary_to_list(<<>>) -> [].

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ How Binaries are Implemented │ │ │ │

    │ │ │ │

    Internally, binaries and bitstrings are implemented in the same way. In this │ │ │ │ section, they are called binaries because that is what they are called in the │ │ │ │ @@ -110,29 +110,29 @@ │ │ │ │ called referential transparency) of Erlang would break.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Constructing Binaries │ │ │ │

    │ │ │ │

    Appending to a binary or bitstring in the following way is specially optimized │ │ │ │ -to avoid copying the binary:

    <<Binary/binary, ...>>
    │ │ │ │ +to avoid copying the binary:

    <<Binary/binary, ...>>
    │ │ │ │  %% - OR -
    │ │ │ │ -<<Binary/bitstring, ...>>

    This optimization is applied by the runtime system in a way that makes it │ │ │ │ +<<Binary/bitstring, ...>>

    This optimization is applied by the runtime system in a way that makes it │ │ │ │ effective in most circumstances (for exceptions, see │ │ │ │ Circumstances That Force Copying). The │ │ │ │ optimization in its basic form does not need any help from the compiler. │ │ │ │ However, the compiler add hints to the runtime system when it is safe to apply │ │ │ │ the optimization in a more efficient way.

    Change

    The compiler support for making the optimization more efficient was added in │ │ │ │ Erlang/OTP 26.

    To explain how the basic optimization works, let us examine the following code │ │ │ │ -line by line:

    Bin0 = <<0>>,                    %% 1
    │ │ │ │ -Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
    │ │ │ │ -Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
    │ │ │ │ -Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
    │ │ │ │ -Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
    │ │ │ │ -{Bin4,Bin3}                      %% 6
    • Line 1 (marked with the %% 1 comment), assigns a │ │ │ │ +line by line:

      Bin0 = <<0>>,                    %% 1
      │ │ │ │ +Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
      │ │ │ │ +Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
      │ │ │ │ +Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
      │ │ │ │ +Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
      │ │ │ │ +{Bin4,Bin3}                      %% 6
      • Line 1 (marked with the %% 1 comment), assigns a │ │ │ │ heap binary to the Bin0 variable.

      • Line 2 is an append operation. As Bin0 has not been involved in an append │ │ │ │ operation, a new refc binary is created and │ │ │ │ the contents of Bin0 is copied into it. The ProcBin part of the refc │ │ │ │ binary has its size set to the size of the data stored in the binary, while │ │ │ │ the binary object has extra space allocated. The size of the binary object is │ │ │ │ either twice the size of Bin1 or 256, whichever is larger. In this case it │ │ │ │ is 256.

      • Line 3 is more interesting. Bin1 has been used in an append operation, and │ │ │ │ @@ -158,23 +158,23 @@ │ │ │ │ handle an append operation to a heap binary by copying it to a refc binary (line │ │ │ │ 2), and also handle an append operation to a previous version of the binary by │ │ │ │ copying it (line 5). The support for doing that does not come for free. For │ │ │ │ example, to make it possible to know when it is necessary to copy the binary, │ │ │ │ for every append operation, the runtime system must create a sub binary.

        When the compiler can determine that none of those situations need to be handled │ │ │ │ and that the append operation cannot possibly fail, the compiler generates code │ │ │ │ that causes the runtime system to apply a more efficient variant of the │ │ │ │ -optimization.

        Example:

        -module(repack).
        │ │ │ │ --export([repack/1]).
        │ │ │ │ +optimization.

        Example:

        -module(repack).
        │ │ │ │ +-export([repack/1]).
        │ │ │ │  
        │ │ │ │ -repack(Bin) when is_binary(Bin) ->
        │ │ │ │ -    repack(Bin, <<>>).
        │ │ │ │ +repack(Bin) when is_binary(Bin) ->
        │ │ │ │ +    repack(Bin, <<>>).
        │ │ │ │  
        │ │ │ │ -repack(<<C:8,T/binary>>, Result) ->
        │ │ │ │ -    repack(T, <<Result/binary,C:16>>);
        │ │ │ │ -repack(<<>>, Result) ->
        │ │ │ │ +repack(<<C:8,T/binary>>, Result) ->
        │ │ │ │ +    repack(T, <<Result/binary,C:16>>);
        │ │ │ │ +repack(<<>>, Result) ->
        │ │ │ │      Result.

        The repack/2 function only keeps a single version of the binary, so there is │ │ │ │ never any need to copy the binary. The compiler rewrites the creation of the │ │ │ │ empty binary in repack/1 to instead create a refc binary with 256 bytes │ │ │ │ already reserved; thus, the append operation in repack/2 never needs to handle │ │ │ │ a binary not prepared for appending.

        │ │ │ │ │ │ │ │ │ │ │ │ @@ -186,72 +186,72 @@ │ │ │ │ reason is that the binary object can be moved (reallocated) during an append │ │ │ │ operation, and when that happens, the pointer in the ProcBin must be updated. If │ │ │ │ there would be more than one ProcBin pointing to the binary object, it would not │ │ │ │ be possible to find and update all of them.

        Therefore, certain operations on a binary mark it so that any future append │ │ │ │ operation will be forced to copy the binary. In most cases, the binary object │ │ │ │ will be shrunk at the same time to reclaim the extra space allocated for │ │ │ │ growing.

        When appending to a binary as follows, only the binary returned from the latest │ │ │ │ -append operation will support further cheap append operations:

        Bin = <<Bin0,...>>

        In the code fragment in the beginning of this section, appending to Bin will │ │ │ │ +append operation will support further cheap append operations:

        Bin = <<Bin0,...>>

        In the code fragment in the beginning of this section, appending to Bin will │ │ │ │ be cheap, while appending to Bin0 will force the creation of a new binary and │ │ │ │ copying of the contents of Bin0.

        If a binary is sent as a message to a process or port, the binary will be shrunk │ │ │ │ and any further append operation will copy the binary data into a new binary. │ │ │ │ For example, in the following code fragment Bin1 will be copied in the third │ │ │ │ -line:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ +line:

        Bin1 = <<Bin0,...>>,
        │ │ │ │  PortOrPid ! Bin1,
        │ │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The same happens if you insert a binary into an Ets table, send it to a port │ │ │ │ +Bin = <<Bin1,...>> %% Bin1 will be COPIED

        The same happens if you insert a binary into an Ets table, send it to a port │ │ │ │ using erlang:port_command/2, or pass it to │ │ │ │ enif_inspect_binary in a NIF.

        Matching a binary will also cause it to shrink and the next append operation │ │ │ │ -will copy the binary data:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ -<<X,Y,Z,T/binary>> = Bin1,
        │ │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The reason is that a match context contains a │ │ │ │ +will copy the binary data:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ +<<X,Y,Z,T/binary>> = Bin1,
        │ │ │ │ +Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The reason is that a match context contains a │ │ │ │ direct pointer to the binary data.

        If a process simply keeps binaries (either in "loop data" or in the process │ │ │ │ dictionary), the garbage collector can eventually shrink the binaries. If only │ │ │ │ one such binary is kept, it will not be shrunk. If the process later appends to │ │ │ │ a binary that has been shrunk, the binary object will be reallocated to make │ │ │ │ place for the data to be appended.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │ │

        │ │ │ │ -

        Let us revisit the example in the beginning of the previous section:

        DO

        my_binary_to_list(<<H,T/binary>>) ->
        │ │ │ │ -    [H|my_binary_to_list(T)];
        │ │ │ │ -my_binary_to_list(<<>>) -> [].

        The first time my_binary_to_list/1 is called, a │ │ │ │ +

        Let us revisit the example in the beginning of the previous section:

        DO

        my_binary_to_list(<<H,T/binary>>) ->
        │ │ │ │ +    [H|my_binary_to_list(T)];
        │ │ │ │ +my_binary_to_list(<<>>) -> [].

        The first time my_binary_to_list/1 is called, a │ │ │ │ match context is created. The match context │ │ │ │ points to the first byte of the binary. 1 byte is matched out and the match │ │ │ │ context is updated to point to the second byte in the binary.

        At this point it would make sense to create a │ │ │ │ sub binary, but in this particular example the │ │ │ │ compiler sees that there will soon be a call to a function (in this case, to │ │ │ │ my_binary_to_list/1 itself) that immediately will create a new match context │ │ │ │ and discard the sub binary.

        Therefore my_binary_to_list/1 calls itself with the match context instead of │ │ │ │ with a sub binary. The instruction that initializes the matching operation │ │ │ │ basically does nothing when it sees that it was passed a match context instead │ │ │ │ of a binary.

        When the end of the binary is reached and the second clause matches, the match │ │ │ │ context will simply be discarded (removed in the next garbage collection, as │ │ │ │ there is no longer any reference to it).

        To summarize, my_binary_to_list/1 only needs to create one match context and │ │ │ │ no sub binaries.

        Notice that the match context in my_binary_to_list/1 was discarded when the │ │ │ │ entire binary had been traversed. What happens if the iteration stops before it │ │ │ │ -has reached the end of the binary? Will the optimization still work?

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +has reached the end of the binary? Will the optimization still work?

        after_zero(<<0,T/binary>>) ->
        │ │ │ │      T;
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -after_zero(<<>>) ->
        │ │ │ │ -    <<>>.

        Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ │ +after_zero(<<_,T/binary>>) -> │ │ │ │ + after_zero(T); │ │ │ │ +after_zero(<<>>) -> │ │ │ │ + <<>>.

        Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ │ second clause:

        ...
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -...

        But it will generate code that builds a sub binary in the first clause:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +after_zero(<<_,T/binary>>) ->
        │ │ │ │ +    after_zero(T);
        │ │ │ │ +...

        But it will generate code that builds a sub binary in the first clause:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │      T;
        │ │ │ │  ...

        Therefore, after_zero/1 builds one match context and one sub binary (assuming │ │ │ │ -it is passed a binary that contains a zero byte).

        Code like the following will also be optimized:

        all_but_zeroes_to_list(Buffer, Acc, 0) ->
        │ │ │ │ -    {lists:reverse(Acc),Buffer};
        │ │ │ │ -all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
        │ │ │ │ -    all_but_zeroes_to_list(T, Acc, Remaining-1);
        │ │ │ │ -all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
        │ │ │ │ -    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

        The compiler removes building of sub binaries in the second and third clauses, │ │ │ │ +it is passed a binary that contains a zero byte).

        Code like the following will also be optimized:

        all_but_zeroes_to_list(Buffer, Acc, 0) ->
        │ │ │ │ +    {lists:reverse(Acc),Buffer};
        │ │ │ │ +all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
        │ │ │ │ +    all_but_zeroes_to_list(T, Acc, Remaining-1);
        │ │ │ │ +all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
        │ │ │ │ +    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

        The compiler removes building of sub binaries in the second and third clauses, │ │ │ │ and it adds an instruction to the first clause that converts Buffer from a │ │ │ │ match context to a sub binary (or do nothing if Buffer is a binary already).

        But in more complicated code, how can one know whether the optimization is │ │ │ │ applied or not?

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Option bin_opt_info │ │ │ │ @@ -259,35 +259,35 @@ │ │ │ │

        Use the bin_opt_info option to have the compiler print a lot of information │ │ │ │ about binary optimizations. It can be given either to the compiler or erlc:

        erlc +bin_opt_info Mod.erl

        or passed through an environment variable:

        export ERL_COMPILER_OPTIONS=bin_opt_info

        Notice that the bin_opt_info is not meant to be a permanent option added to │ │ │ │ your Makefiles, because all messages that it generates cannot be eliminated. │ │ │ │ Therefore, passing the option through the environment is in most cases the most │ │ │ │ practical approach.

        The warnings look as follows:

        ./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: binary is returned from the function
        │ │ │ │  ./efficiency_guide.erl:62: Warning: OPTIMIZED: match context reused

        To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ │ -example:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +example:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │           %% BINARY CREATED: binary is returned from the function
        │ │ │ │      T;
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ +after_zero(<<_,T/binary>>) ->
        │ │ │ │           %% OPTIMIZED: match context reused
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -after_zero(<<>>) ->
        │ │ │ │ -    <<>>.

        The warning for the first clause says that the creation of a sub binary cannot │ │ │ │ + after_zero(T); │ │ │ │ +after_zero(<<>>) -> │ │ │ │ + <<>>.

        The warning for the first clause says that the creation of a sub binary cannot │ │ │ │ be delayed, because it will be returned. The warning for the second clause says │ │ │ │ that a sub binary will not be created (yet).

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Unused Variables │ │ │ │

        │ │ │ │

        The compiler figures out if a variable is unused. The same code is generated for │ │ │ │ -each of the following functions:

        count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
        │ │ │ │ -count1(<<>>, Count) -> Count.
        │ │ │ │ +each of the following functions:

        count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
        │ │ │ │ +count1(<<>>, Count) -> Count.
        │ │ │ │  
        │ │ │ │ -count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
        │ │ │ │ -count2(<<>>, Count) -> Count.
        │ │ │ │ +count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
        │ │ │ │ +count2(<<>>, Count) -> Count.
        │ │ │ │  
        │ │ │ │ -count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
        │ │ │ │ -count3(<<>>, Count) -> Count.

        In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ │ +count3(<<_H,T/binary>>, Count) -> count3(T, Count+1); │ │ │ │ +count3(<<>>, Count) -> Count.

        In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ │ out.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/benchmarking.xhtml │ │ │ │ @@ -49,16 +49,16 @@ │ │ │ │ fast as possible, what can we do? One way could be to generate more │ │ │ │ than two bytes at the time.

        % erlperf 'rand:bytes(100).' 'crypto:strong_rand_bytes(100).'
        │ │ │ │  Code                                   ||        QPS       Time   Rel
        │ │ │ │  rand:bytes(100).                        1    2124 Ki     470 ns  100%
        │ │ │ │  crypto:strong_rand_bytes(100).          1    1915 Ki     522 ns   90%

        rand:bytes/1 is still faster when we generate 100 bytes at the time, │ │ │ │ but the relative difference is smaller.

        % erlperf 'rand:bytes(1000).' 'crypto:strong_rand_bytes(1000).'
        │ │ │ │  Code                                    ||        QPS       Time   Rel
        │ │ │ │ -crypto:strong_rand_bytes(1000).          1    1518 Ki     658 ns  100%
        │ │ │ │ -rand:bytes(1000).                        1     284 Ki    3521 ns   19%

        When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ │ +crypto:strong_rand_bytes(1000). 1 1518 Ki 658 ns 100% │ │ │ │ +rand:bytes(1000). 1 284 Ki 3521 ns 19%

        When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ │ now the fastest.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Benchmarking using Erlang/OTP functionality │ │ │ │

        │ │ │ │

        Benchmarks can measure wall-clock time or CPU time.

        • timer:tc/3 measures wall-clock time. The advantage with wall-clock time is │ │ │ ├── OEBPS/appup_cookbook.xhtml │ │ │ │ @@ -25,18 +25,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Functional Module │ │ │ │ │ │ │ │

          When a functional module has been changed, for example, if a new function has │ │ │ │ been added or a bug has been corrected, simple code replacement is sufficient, │ │ │ │ -for example:

          {"2",
          │ │ │ │ - [{"1", [{load_module, m}]}],
          │ │ │ │ - [{"1", [{load_module, m}]}]
          │ │ │ │ -}.

          │ │ │ │ +for example:

          {"2",
          │ │ │ │ + [{"1", [{load_module, m}]}],
          │ │ │ │ + [{"1", [{load_module, m}]}]
          │ │ │ │ +}.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Residence Module │ │ │ │

          │ │ │ │

          In a system implemented according to the OTP design principles, all processes, │ │ │ │ except system processes and special processes, reside in one of the behaviours │ │ │ │ @@ -47,46 +47,46 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Callback Module │ │ │ │ │ │ │ │

          A callback module is a functional module, and for code extensions simple code │ │ │ │ replacement is sufficient.

          Example

          When adding a function to ch3, as described in the example in │ │ │ │ -Release Handling, ch_app.appup looks as follows:

          {"2",
          │ │ │ │ - [{"1", [{load_module, ch3}]}],
          │ │ │ │ - [{"1", [{load_module, ch3}]}]
          │ │ │ │ -}.

          OTP also supports changing the internal state of behaviour processes; see │ │ │ │ +Release Handling, ch_app.appup looks as follows:

          {"2",
          │ │ │ │ + [{"1", [{load_module, ch3}]}],
          │ │ │ │ + [{"1", [{load_module, ch3}]}]
          │ │ │ │ +}.

          OTP also supports changing the internal state of behaviour processes; see │ │ │ │ Changing Internal State.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Internal State │ │ │ │

          │ │ │ │

          In this case, simple code replacement is not sufficient. The process must │ │ │ │ explicitly transform its state using the callback function code_change/3 before │ │ │ │ switching to the new version of the callback module. Thus, synchronized code │ │ │ │ replacement is used.

          Example

          Consider the ch3 module from │ │ │ │ gen_server Behaviour. The internal state is a term │ │ │ │ Chs representing the available channels. Assume you want to add a counter N, │ │ │ │ which keeps track of the number of alloc requests so far. This means that the │ │ │ │ -format must be changed to {Chs,N}.

          The .appup file can look as follows:

          {"2",
          │ │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}],
          │ │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}]
          │ │ │ │ -}.

          The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ │ +format must be changed to {Chs,N}.

          The .appup file can look as follows:

          {"2",
          │ │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}],
          │ │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}]
          │ │ │ │ +}.

          The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ │ which says that the affected processes are to do a state transformation before │ │ │ │ loading the new version of the module. This is done by the processes calling the │ │ │ │ callback function code_change/3 (see gen_server in STDLIB). │ │ │ │ -The term Extra, in this case [], is passed as is to the function:

          -module(ch3).
          │ │ │ │ +The term Extra, in this case [], is passed as is to the function:

          -module(ch3).
          │ │ │ │  ...
          │ │ │ │ --export([code_change/3]).
          │ │ │ │ +-export([code_change/3]).
          │ │ │ │  ...
          │ │ │ │ -code_change({down, _Vsn}, {Chs, N}, _Extra) ->
          │ │ │ │ -    {ok, Chs};
          │ │ │ │ -code_change(_Vsn, Chs, _Extra) ->
          │ │ │ │ -    {ok, {Chs, 0}}.

          The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ │ +code_change({down, _Vsn}, {Chs, N}, _Extra) -> │ │ │ │ + {ok, Chs}; │ │ │ │ +code_change(_Vsn, Chs, _Extra) -> │ │ │ │ + {ok, {Chs, 0}}.

          The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ │ a upgrade. The term Vsn is fetched from the 'original' version of the module, │ │ │ │ that is, the version you are upgrading from, or downgrading to.

          The version is defined by the module attribute vsn, if any. There is no such │ │ │ │ attribute in ch3, so in this case the version is the checksum (a huge integer) │ │ │ │ of the beam file, an uninteresting value, which is ignored.

          The other callback functions of ch3 must also be modified and perhaps a new │ │ │ │ interface function must be added, but this is not shown here.

          │ │ │ │ │ │ │ │ │ │ │ │ @@ -95,67 +95,67 @@ │ │ │ │

          │ │ │ │

          Assume that a module is extended by adding an interface function, as in the │ │ │ │ example in Release Handling, where a function │ │ │ │ available/0 is added to ch3.

          If a call is added to this function, say in module m1, a runtime error could │ │ │ │ can occur during release upgrade if the new version of m1 is loaded first and │ │ │ │ calls ch3:available/0 before the new version of ch3 is loaded.

          Thus, ch3 must be loaded before m1, in the upgrade case, and conversely in │ │ │ │ the downgrade case. m1 is said to be dependent on ch3. In a release │ │ │ │ -handling instruction, this is expressed by the DepMods element:

          {load_module, Module, DepMods}
          │ │ │ │ -{update, Module, {advanced, Extra}, DepMods}

          DepMods is a list of modules, on which Module is dependent.

          Example

          The module m1 in application myapp is dependent on ch3 when │ │ │ │ +handling instruction, this is expressed by the DepMods element:

          {load_module, Module, DepMods}
          │ │ │ │ +{update, Module, {advanced, Extra}, DepMods}

          DepMods is a list of modules, on which Module is dependent.

          Example

          The module m1 in application myapp is dependent on ch3 when │ │ │ │ upgrading from "1" to "2", or downgrading from "2" to "1":

          myapp.appup:
          │ │ │ │  
          │ │ │ │ -{"2",
          │ │ │ │ - [{"1", [{load_module, m1, [ch3]}]}],
          │ │ │ │ - [{"1", [{load_module, m1, [ch3]}]}]
          │ │ │ │ -}.
          │ │ │ │ +{"2",
          │ │ │ │ + [{"1", [{load_module, m1, [ch3]}]}],
          │ │ │ │ + [{"1", [{load_module, m1, [ch3]}]}]
          │ │ │ │ +}.
          │ │ │ │  
          │ │ │ │  ch_app.appup:
          │ │ │ │  
          │ │ │ │ -{"2",
          │ │ │ │ - [{"1", [{load_module, ch3}]}],
          │ │ │ │ - [{"1", [{load_module, ch3}]}]
          │ │ │ │ -}.

          If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ │ -look as follows:

          {"2",
          │ │ │ │ - [{"1",
          │ │ │ │ -   [{load_module, ch3},
          │ │ │ │ -    {load_module, m1, [ch3]}]}],
          │ │ │ │ - [{"1",
          │ │ │ │ -   [{load_module, ch3},
          │ │ │ │ -    {load_module, m1, [ch3]}]}]
          │ │ │ │ -}.

          m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ │ +{"2", │ │ │ │ + [{"1", [{load_module, ch3}]}], │ │ │ │ + [{"1", [{load_module, ch3}]}] │ │ │ │ +}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ │ +look as follows:

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{load_module, ch3},
    │ │ │ │ +    {load_module, m1, [ch3]}]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{load_module, ch3},
    │ │ │ │ +    {load_module, m1, [ch3]}]}]
    │ │ │ │ +}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ │ difference between up- and downgrading and generates a correct relup, where │ │ │ │ ch3 is loaded before m1 when upgrading, but m1 is loaded before ch3 when │ │ │ │ downgrading.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Code for a Special Process │ │ │ │

    │ │ │ │

    In this case, simple code replacement is not sufficient. When a new version of a │ │ │ │ residence module for a special process is loaded, the process must make a fully │ │ │ │ qualified call to its loop function to switch to the new code. Thus, │ │ │ │ synchronized code replacement must be used.

    Note

    The name(s) of the user-defined residence module(s) must be listed in the │ │ │ │ Modules part of the child specification for the special process. Otherwise │ │ │ │ the release handler cannot find the process.

    Example

    Consider the example ch4 in sys and proc_lib. │ │ │ │ -When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ │ - permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ │ +When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ │ + permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ │ to be loaded when upgrading from version "1" to "2" of this application, │ │ │ │ -sp_app.appup can look as follows:

    {"2",
    │ │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ │ -}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ │ +sp_app.appup can look as follows:

    {"2",
    │ │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ │ +}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ │ instruction makes the special process call the callback function │ │ │ │ system_code_change/4, a function the user must implement. The term Extra, in │ │ │ │ -this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ │ +this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ │  ...
    │ │ │ │ --export([system_code_change/4]).
    │ │ │ │ +-export([system_code_change/4]).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -system_code_change(Chs, _Module, _OldVsn, _Extra) ->
    │ │ │ │ -    {ok, Chs}.
    • The first argument is the internal state State, passed from │ │ │ │ +system_code_change(Chs, _Module, _OldVsn, _Extra) -> │ │ │ │ + {ok, Chs}.

    In this case, all arguments but the first are ignored and the function simply │ │ │ │ returns the internal state again. This is enough if the code only has been │ │ │ │ extended. If instead the internal state is changed (similar to the example in │ │ │ │ @@ -176,85 +176,85 @@ │ │ │ │ Changing Properties │ │ │ │ │ │ │ │

    Since the supervisor is to change its internal state, synchronized code │ │ │ │ replacement is required. However, a special update instruction must be used.

    First, the new version of the callback module must be loaded, both in the case │ │ │ │ of upgrade and downgrade. Then the new return value of init/1 can be checked │ │ │ │ and the internal state be changed accordingly.

    The following upgrade instruction is used for supervisors:

    {update, Module, supervisor}

    Example

    To change the restart strategy of ch_sup (from │ │ │ │ Supervisor Behaviour) from one_for_one to one_for_all, │ │ │ │ -change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ │ +change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -init(_Args) ->
    │ │ │ │ -    {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ -}.

    │ │ │ │ +init(_Args) -> │ │ │ │ + {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Child Specifications │ │ │ │

    │ │ │ │

    The instruction, and thus the .appup file, when changing an existing child │ │ │ │ -specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ -}.

    The changes do not affect existing child processes. For example, changing the │ │ │ │ +specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ +}.

    The changes do not affect existing child processes. For example, changing the │ │ │ │ start function only specifies how the child process is to be restarted, if │ │ │ │ needed later on.

    The id of the child specification cannot be changed.

    Changing the Modules field of the child specification can affect the release │ │ │ │ handling process itself, as this field is used to identify which processes are │ │ │ │ affected when doing a synchronized code replacement.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding and Deleting Child Processes │ │ │ │

    │ │ │ │

    As stated earlier, changing child specifications does not affect existing child │ │ │ │ processes. New child specifications are automatically added, but not deleted. │ │ │ │ Child processes are not automatically started or terminated, this must be done │ │ │ │ using apply instructions.

    Example

    Assume a new child process m1 is to be added to ch_sup when │ │ │ │ upgrading ch_app from "1" to "2". This means m1 is to be deleted when │ │ │ │ -downgrading from "2" to "1":

    {"2",
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{update, ch_sup, supervisor},
    │ │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ -   ]}],
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ -    {update, ch_sup, supervisor}
    │ │ │ │ -   ]}]
    │ │ │ │ -}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ │ +downgrading from "2" to "1":

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{update, ch_sup, supervisor},
    │ │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ +   ]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ +    {update, ch_sup, supervisor}
    │ │ │ │ +   ]}]
    │ │ │ │ +}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ │ supervisor is not registered, it cannot be accessed directly from the script. │ │ │ │ Instead a help function that finds the pid of the supervisor and calls │ │ │ │ supervisor:restart_child, and so on, must be written. This function is then to │ │ │ │ be called from the script using the apply instruction.

    If the module m1 is introduced in version "2" of ch_app, it must also be │ │ │ │ -loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{add_module, m1},
    │ │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ -   ]}],
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ │ -    {delete_module, m1}
    │ │ │ │ -   ]}]
    │ │ │ │ -}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ │ +loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{add_module, m1},
    │ │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ +   ]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ │ +    {delete_module, m1}
    │ │ │ │ +   ]}]
    │ │ │ │ +}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ │ m1 must be loaded, and the supervisor child specification changed, before the │ │ │ │ new child process can be started. When downgrading, the child process must be │ │ │ │ terminated before the child specification is changed and the module is deleted.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding or Deleting a Module │ │ │ │

    │ │ │ │ -

    _Example

    _ A new functional module m is added to ch_app:

    {"2",
    │ │ │ │ - [{"1", [{add_module, m}]}],
    │ │ │ │ - [{"1", [{delete_module, m}]}]

    │ │ │ │ +

    _Example

    _ A new functional module m is added to ch_app:

    {"2",
    │ │ │ │ + [{"1", [{add_module, m}]}],
    │ │ │ │ + [{"1", [{delete_module, m}]}]

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting or Terminating a Process │ │ │ │

    │ │ │ │

    In a system structured according to the OTP design principles, any process would │ │ │ │ be a child process belonging to a supervisor, see │ │ │ │ @@ -274,29 +274,29 @@ │ │ │ │ Restarting an Application │ │ │ │ │ │ │ │

    Restarting an application is useful when a change is too complicated to be made │ │ │ │ without restarting the processes, for example, if the supervisor hierarchy has │ │ │ │ been restructured.

    Example

    When adding a child m1 to ch_sup, as in │ │ │ │ Adding and Deleting Child Processes in Changing a │ │ │ │ Supervisor, an alternative to updating the supervisor is to restart the entire │ │ │ │ -application:

    {"2",
    │ │ │ │ - [{"1", [{restart_application, ch_app}]}],
    │ │ │ │ - [{"1", [{restart_application, ch_app}]}]
    │ │ │ │ -}.

    │ │ │ │ +application:

    {"2",
    │ │ │ │ + [{"1", [{restart_application, ch_app}]}],
    │ │ │ │ + [{"1", [{restart_application, ch_app}]}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing an Application Specification │ │ │ │

    │ │ │ │

    When installing a release, the application specifications are automatically │ │ │ │ updated before evaluating the relup script. Thus, no instructions are needed │ │ │ │ -in the .appup file:

    {"2",
    │ │ │ │ - [{"1", []}],
    │ │ │ │ - [{"1", []}]
    │ │ │ │ -}.

    │ │ │ │ +in the .appup file:

    {"2",
    │ │ │ │ + [{"1", []}],
    │ │ │ │ + [{"1", []}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Application Configuration │ │ │ │

    │ │ │ │

    Changing an application configuration by updating the env key in the .app │ │ │ │ file is an instance of changing an application specification, see the previous │ │ │ │ @@ -311,26 +311,26 @@ │ │ │ │ applications apply to primary applications only. There are no corresponding │ │ │ │ instructions for included applications. However, since an included application │ │ │ │ is really a supervision tree with a topmost supervisor, started as a child │ │ │ │ process to a supervisor in the including application, a .relup file can be │ │ │ │ manually created.

    Example

    Assume there is a release containing an application prim_app, which │ │ │ │ have a supervisor prim_sup in its supervision tree.

    In a new version of the release, the application ch_app is to be included in │ │ │ │ prim_app. That is, its topmost supervisor ch_sup is to be started as a child │ │ │ │ -process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ │ -    {ok, {...supervisor flags...,
    │ │ │ │ -          [...,
    │ │ │ │ -           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ │ -            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ │ -           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ │ - [...,
    │ │ │ │ -  {vsn, "2"},
    │ │ │ │ +process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ │ +    {ok, {...supervisor flags...,
    │ │ │ │ +          [...,
    │ │ │ │ +           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ │ +            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ │ +           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ │ + [...,
    │ │ │ │ +  {vsn, "2"},
    │ │ │ │    ...,
    │ │ │ │ -  {included_applications, [ch_app]},
    │ │ │ │ +  {included_applications, [ch_app]},
    │ │ │ │    ...
    │ │ │ │ - ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ │ + ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ │   ...,
    │ │ │ │   [...,
    │ │ │ │    {prim_app, "2"},
    │ │ │ │    {ch_app, "1"}]}.

    The included application can be started in two ways. This is described in the │ │ │ │ next two sections.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -385,74 +385,74 @@ │ │ │ │

    Step 4b) Another way to start the included application (or stop it in the case │ │ │ │ of downgrade) is by combining instructions for adding and removing child │ │ │ │ processes to/from prim_sup with instructions for loading/unloading all │ │ │ │ ch_app code and its application specification.

    Again, the .relup file is created manually, either from scratch or by editing a │ │ │ │ generated version. Load all code for ch_app first, and also load the │ │ │ │ application specification, before prim_sup is updated. When downgrading, │ │ │ │ prim_sup is to updated first, before the code for ch_app and its application │ │ │ │ -specification are unloaded.

    {"B",
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ -    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ │ +specification are unloaded.

    {"B",
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ +    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ │ -    {suspend,[prim_sup]},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {code_change,up,[{prim_sup,[]}]},
    │ │ │ │ -    {resume,[prim_sup]},
    │ │ │ │ -    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ │ +    {suspend,[prim_sup]},
    │ │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {code_change,up,[{prim_sup,[]}]},
    │ │ │ │ +    {resume,[prim_sup]},
    │ │ │ │ +    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}},
    │ │ │ │ -    {apply,{supervisor,delete_child,[prim_sup,ch_sup]}},
    │ │ │ │ -    {suspend,[prim_sup]},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {code_change,down,[{prim_sup,[]}]},
    │ │ │ │ -    {resume,[prim_sup]},
    │ │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ │ -    {apply,{application,unload,[ch_app]}}]}]
    │ │ │ │ -}.

    │ │ │ │ + {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, │ │ │ │ + {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, │ │ │ │ + {suspend,[prim_sup]}, │ │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {code_change,down,[{prim_sup,[]}]}, │ │ │ │ + {resume,[prim_sup]}, │ │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ │ + {apply,{application,unload,[ch_app]}}]}] │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Non-Erlang Code │ │ │ │

    │ │ │ │

    Changing code for a program written in another programming language than Erlang, │ │ │ │ for example, a port program, is application-dependent and OTP provides no │ │ │ │ special support.

    Example

    When changing code for a port program, assume that the Erlang process │ │ │ │ controlling the port is a gen_server portc and that the port is opened in │ │ │ │ -the callback function init/1:

    init(...) ->
    │ │ │ │ +the callback function init/1:

    init(...) ->
    │ │ │ │      ...,
    │ │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │      ...,
    │ │ │ │ -    {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ │ + {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ │ extended with a code_change/3 function, which closes the old port and opens a │ │ │ │ new port. (If necessary, the gen_server can first request data that must be │ │ │ │ -saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ │ +saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ │      State#state.port ! close,
    │ │ │ │      receive
    │ │ │ │ -        {Port,close} ->
    │ │ │ │ +        {Port,close} ->
    │ │ │ │              true
    │ │ │ │      end,
    │ │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ -    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ │ -file:

    ["2",
    │ │ │ │ - [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ │ - [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ │ -].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ │ -the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ +    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ │ +file:

    ["2",
    │ │ │ │ + [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ │ + [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ │ +].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ │ +the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ │  ...

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Runtime System Restart and Upgrade │ │ │ │

    │ │ │ │

    Two upgrade instructions restart the runtime system:

    • restart_new_emulator

      Intended when ERTS, Kernel, STDLIB, or SASL is upgraded. It is automatically │ │ │ │ @@ -460,20 +460,20 @@ │ │ │ │ executed before all other upgrade instructions. For more information about │ │ │ │ this instruction, see restart_new_emulator (Low-Level) in │ │ │ │ Release Handling Instructions.

    • restart_emulator

      Used when a restart of the runtime system is required after all other upgrade │ │ │ │ instructions are executed. For more information about this instruction, see │ │ │ │ restart_emulator (Low-Level) in │ │ │ │ Release Handling Instructions.

    If a runtime system restart is necessary and no upgrade instructions are needed, │ │ │ │ that is, if the restart itself is enough for the upgraded applications to start │ │ │ │ -running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [restart_emulator]}],
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [restart_emulator]}]
    │ │ │ │ -}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ │ +running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [restart_emulator]}],
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [restart_emulator]}]
    │ │ │ │ +}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ │ of release packages, automatic path updates, and so on, can be used without │ │ │ │ having to specify .appup files.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/applications.xhtml │ │ │ │ @@ -40,34 +40,34 @@ │ │ │ │ directory structure.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Callback Module │ │ │ │

    │ │ │ │

    How to start and stop the code for the application, including its supervision │ │ │ │ -tree, is described by two callback functions:

    start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
    │ │ │ │ -stop(State)
    • start/2 is called when starting the application and is to create the │ │ │ │ +tree, is described by two callback functions:

      start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
      │ │ │ │ +stop(State)
      • start/2 is called when starting the application and is to create the │ │ │ │ supervision tree by starting the top supervisor. It is expected to return the │ │ │ │ pid of the top supervisor and an optional term, State, which defaults to │ │ │ │ []. This term is passed as is to stop/1.
      • StartType is usually the atom normal. It has other values only in the case │ │ │ │ of a takeover or failover; see │ │ │ │ Distributed Applications.
      • StartArgs is defined by the key mod in the │ │ │ │ application resource file.
      • stop/1 is called after the application has been stopped and is to do any │ │ │ │ necessary cleaning up. The actual stopping of the application, that is, │ │ │ │ shutting down the supervision tree, is handled automatically as described in │ │ │ │ Starting and Stopping Applications.

      Example of an application callback module for packaging the supervision tree │ │ │ │ -from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ │ --behaviour(application).
      │ │ │ │ +from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ │ +-behaviour(application).
      │ │ │ │  
      │ │ │ │ --export([start/2, stop/1]).
      │ │ │ │ +-export([start/2, stop/1]).
      │ │ │ │  
      │ │ │ │ -start(_Type, _Args) ->
      │ │ │ │ -    ch_sup:start_link().
      │ │ │ │ +start(_Type, _Args) ->
      │ │ │ │ +    ch_sup:start_link().
      │ │ │ │  
      │ │ │ │ -stop(_State) ->
      │ │ │ │ +stop(_State) ->
      │ │ │ │      ok.

      A library application that cannot be started or stopped does not need any │ │ │ │ application callback module.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Resource File │ │ │ │

      │ │ │ │ @@ -78,22 +78,22 @@ │ │ │ │ keys.

    The contents of a minimal .app file for a library application libapp looks │ │ │ │ as follows:

    {application, libapp, []}.

    The contents of a minimal .app file ch_app.app for a supervision tree │ │ │ │ application like ch_app looks as follows:

    {application, ch_app,
    │ │ │ │   [{mod, {ch_app,[]}}]}.

    The key mod defines the callback module and start argument of the application, │ │ │ │ in this case ch_app and [], respectively. This means that the following is │ │ │ │ called when the application is to be started:

    ch_app:start(normal, [])

    The following is called when the application is stopped:

    ch_app:stop([])

    When using systools, the Erlang/OTP tools for packaging code (see Section │ │ │ │ Releases), the keys description, vsn, modules, │ │ │ │ -registered, and applications are also to be specified:

    {application, ch_app,
    │ │ │ │ - [{description, "Channel allocator"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ │ -  {registered, [ch3]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ │ - ]}.
    • description - A short description, a string. Defaults to "".
    • vsn - Version number, a string. Defaults to "".
    • modules - All modules introduced by this application. systools uses │ │ │ │ +registered, and applications are also to be specified:

      {application, ch_app,
      │ │ │ │ + [{description, "Channel allocator"},
      │ │ │ │ +  {vsn, "1"},
      │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ +  {registered, [ch3]},
      │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ +  {mod, {ch_app,[]}}
      │ │ │ │ + ]}.
      • description - A short description, a string. Defaults to "".
      • vsn - Version number, a string. Defaults to "".
      • modules - All modules introduced by this application. systools uses │ │ │ │ this list when generating boot scripts and tar files. A module must only │ │ │ │ be included in one application. Defaults to [].
      • registered - All names of registered processes in the application. │ │ │ │ systools uses this list to detect name clashes between applications. │ │ │ │ Defaults to [].
      • applications - All applications that must be started before this │ │ │ │ application is started. systools uses this list to generate correct boot │ │ │ │ scripts. Defaults to []. Notice that all applications have dependencies to │ │ │ │ at least Kernel and STDLIB.

      Note

      For details about the syntax and contents of the application resource file, │ │ │ │ @@ -205,38 +205,38 @@ │ │ │ │ stop applications.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Loading and Unloading Applications │ │ │ │

      │ │ │ │

      Before an application can be started, it must be loaded. The application │ │ │ │ -controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ │ +controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ │  ok
      │ │ │ │ -2> application:loaded_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ │ - {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ │ +2> application:loaded_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ │ + {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ │ unloaded. The information about the application is erased from the internal │ │ │ │ -database of the application controller.

      3> application:unload(ch_app).
      │ │ │ │ +database of the application controller.

      3> application:unload(ch_app).
      │ │ │ │  ok
      │ │ │ │ -4> application:loaded_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ │ +4> application:loaded_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ │ application. Code loading is handled in the usual way by the code server.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Applications │ │ │ │

      │ │ │ │ -

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ │ +

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ │  ok
      │ │ │ │ -6> application:which_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ │ - {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ │ +6> application:which_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ │ + {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ │ it using application:load/1. It checks the value of the applications key to │ │ │ │ ensure that all applications that are to be started before this application are │ │ │ │ running.

      Following that, the application controller creates an application master for │ │ │ │ the application.

      The application master establishes itself as the group │ │ │ │ leader of all processes in the application │ │ │ │ and will forward I/O to the previous group leader.

      Note

      The purpose of the application master being the group leader is to easily │ │ │ │ keep track of which processes that belong to the application. That is needed │ │ │ │ @@ -252,55 +252,55 @@ │ │ │ │ defined by the mod key.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Configuring an Application │ │ │ │

      │ │ │ │

      An application can be configured using configuration parameters. These are a │ │ │ │ -list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ │ - [{description, "Channel allocator"},
      │ │ │ │ -  {vsn, "1"},
      │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ -  {registered, [ch3]},
      │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ -  {mod, {ch_app,[]}},
      │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
      │ │ │ │ - ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ │ +list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ │ + [{description, "Channel allocator"},
      │ │ │ │ +  {vsn, "1"},
      │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ +  {registered, [ch3]},
      │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ +  {mod, {ch_app,[]}},
      │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
      │ │ │ │ + ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ │ value of a configuration parameter by calling application:get_env(App, Par) or │ │ │ │ a number of similar functions. For more information, see module application │ │ │ │ in Kernel.

      Example:

      % erl
      │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ │  
      │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
      │ │ │ │ -1> application:start(ch_app).
      │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
      │ │ │ │ +1> application:start(ch_app).
      │ │ │ │  ok
      │ │ │ │ -2> application:get_env(ch_app, file).
      │ │ │ │ -{ok,"/usr/local/log"}

      The values in the .app file can be overridden by values in a system │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"/usr/local/log"}

    The values in the .app file can be overridden by values in a system │ │ │ │ configuration file. This is a file that contains configuration parameters for │ │ │ │ -relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ │ +relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ │   ...,
    │ │ │ │ - {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ │ + {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ │ started with the command-line argument -config Name. For details, see │ │ │ │ config in Kernel.

    Example:

    A file test.config is created with the following contents:

    [{ch_app, [{file, "testlog"}]}].

    The value of file overrides the value of file as defined in the .app file:

    % erl -config test
    │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ -1> application:start(ch_app).
    │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ +1> application:start(ch_app).
    │ │ │ │  ok
    │ │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ │ -{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ │ configuration file is to be used and that file is to be called sys.config.

    The values in the .app file and the values in a system configuration file can │ │ │ │ be overridden directly from the command line:

    % erl -ApplName Par1 Val1 ... ParN ValN

    Example:

    % erl -ch_app file '"testlog"'
    │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ -1> application:start(ch_app).
    │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ +1> application:start(ch_app).
    │ │ │ │  ok
    │ │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ │ -{ok,"testlog"}

    │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"testlog"}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Start Types │ │ │ │

    │ │ │ │

    A start type is defined when starting the application:

    application:start(Application, Type)

    application:start(Application) is the same as calling │ │ │ │ application:start(Application, temporary). The type can also be permanent or │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ @@ -135,34 +135,34 @@ │ │ │ directory structure.

    │ │ │ │ │ │ │ │ │ │ │ │ Application Callback Module │ │ │

    │ │ │

    How to start and stop the code for the application, including its supervision │ │ │ -tree, is described by two callback functions:

    start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
    │ │ │ -stop(State)
    • start/2 is called when starting the application and is to create the │ │ │ +tree, is described by two callback functions:

      start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
      │ │ │ +stop(State)
      • start/2 is called when starting the application and is to create the │ │ │ supervision tree by starting the top supervisor. It is expected to return the │ │ │ pid of the top supervisor and an optional term, State, which defaults to │ │ │ []. This term is passed as is to stop/1.
      • StartType is usually the atom normal. It has other values only in the case │ │ │ of a takeover or failover; see │ │ │ Distributed Applications.
      • StartArgs is defined by the key mod in the │ │ │ application resource file.
      • stop/1 is called after the application has been stopped and is to do any │ │ │ necessary cleaning up. The actual stopping of the application, that is, │ │ │ shutting down the supervision tree, is handled automatically as described in │ │ │ Starting and Stopping Applications.

      Example of an application callback module for packaging the supervision tree │ │ │ -from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ --behaviour(application).
      │ │ │ +from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ +-behaviour(application).
      │ │ │  
      │ │ │ --export([start/2, stop/1]).
      │ │ │ +-export([start/2, stop/1]).
      │ │ │  
      │ │ │ -start(_Type, _Args) ->
      │ │ │ -    ch_sup:start_link().
      │ │ │ +start(_Type, _Args) ->
      │ │ │ +    ch_sup:start_link().
      │ │ │  
      │ │ │ -stop(_State) ->
      │ │ │ +stop(_State) ->
      │ │ │      ok.

      A library application that cannot be started or stopped does not need any │ │ │ application callback module.

      │ │ │ │ │ │ │ │ │ │ │ │ Application Resource File │ │ │

      │ │ │ @@ -173,22 +173,22 @@ │ │ │ keys.

    The contents of a minimal .app file for a library application libapp looks │ │ │ as follows:

    {application, libapp, []}.

    The contents of a minimal .app file ch_app.app for a supervision tree │ │ │ application like ch_app looks as follows:

    {application, ch_app,
    │ │ │   [{mod, {ch_app,[]}}]}.

    The key mod defines the callback module and start argument of the application, │ │ │ in this case ch_app and [], respectively. This means that the following is │ │ │ called when the application is to be started:

    ch_app:start(normal, [])

    The following is called when the application is stopped:

    ch_app:stop([])

    When using systools, the Erlang/OTP tools for packaging code (see Section │ │ │ Releases), the keys description, vsn, modules, │ │ │ -registered, and applications are also to be specified:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.
    • description - A short description, a string. Defaults to "".
    • vsn - Version number, a string. Defaults to "".
    • modules - All modules introduced by this application. systools uses │ │ │ +registered, and applications are also to be specified:

      {application, ch_app,
      │ │ │ + [{description, "Channel allocator"},
      │ │ │ +  {vsn, "1"},
      │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ +  {registered, [ch3]},
      │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ +  {mod, {ch_app,[]}}
      │ │ │ + ]}.
      • description - A short description, a string. Defaults to "".
      • vsn - Version number, a string. Defaults to "".
      • modules - All modules introduced by this application. systools uses │ │ │ this list when generating boot scripts and tar files. A module must only │ │ │ be included in one application. Defaults to [].
      • registered - All names of registered processes in the application. │ │ │ systools uses this list to detect name clashes between applications. │ │ │ Defaults to [].
      • applications - All applications that must be started before this │ │ │ application is started. systools uses this list to generate correct boot │ │ │ scripts. Defaults to []. Notice that all applications have dependencies to │ │ │ at least Kernel and STDLIB.

      Note

      For details about the syntax and contents of the application resource file, │ │ │ @@ -300,38 +300,38 @@ │ │ │ stop applications.

      │ │ │ │ │ │ │ │ │ │ │ │ Loading and Unloading Applications │ │ │

      │ │ │

      Before an application can be started, it must be loaded. The application │ │ │ -controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ +controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │  ok
      │ │ │ -2> application:loaded_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ - {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ +2> application:loaded_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ + {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ unloaded. The information about the application is erased from the internal │ │ │ -database of the application controller.

      3> application:unload(ch_app).
      │ │ │ +database of the application controller.

      3> application:unload(ch_app).
      │ │ │  ok
      │ │ │ -4> application:loaded_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ +4> application:loaded_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ application. Code loading is handled in the usual way by the code server.

      │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Applications │ │ │

      │ │ │ -

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ +

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │  ok
      │ │ │ -6> application:which_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ - {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ +6> application:which_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ + {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ it using application:load/1. It checks the value of the applications key to │ │ │ ensure that all applications that are to be started before this application are │ │ │ running.

      Following that, the application controller creates an application master for │ │ │ the application.

      The application master establishes itself as the group │ │ │ leader of all processes in the application │ │ │ and will forward I/O to the previous group leader.

      Note

      The purpose of the application master being the group leader is to easily │ │ │ keep track of which processes that belong to the application. That is needed │ │ │ @@ -347,55 +347,55 @@ │ │ │ defined by the mod key.

      │ │ │ │ │ │ │ │ │ │ │ │ Configuring an Application │ │ │

      │ │ │

      An application can be configured using configuration parameters. These are a │ │ │ -list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ - [{description, "Channel allocator"},
      │ │ │ -  {vsn, "1"},
      │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ -  {registered, [ch3]},
      │ │ │ -  {applications, [kernel, stdlib, sasl]},
      │ │ │ -  {mod, {ch_app,[]}},
      │ │ │ -  {env, [{file, "/usr/local/log"}]}
      │ │ │ - ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ +list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ + [{description, "Channel allocator"},
      │ │ │ +  {vsn, "1"},
      │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ +  {registered, [ch3]},
      │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ +  {mod, {ch_app,[]}},
      │ │ │ +  {env, [{file, "/usr/local/log"}]}
      │ │ │ + ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ value of a configuration parameter by calling application:get_env(App, Par) or │ │ │ a number of similar functions. For more information, see module application │ │ │ in Kernel.

      Example:

      % erl
      │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │  
      │ │ │ -Eshell V5.2.3.6  (abort with ^G)
      │ │ │ -1> application:start(ch_app).
      │ │ │ +Eshell V5.2.3.6  (abort with ^G)
      │ │ │ +1> application:start(ch_app).
      │ │ │  ok
      │ │ │ -2> application:get_env(ch_app, file).
      │ │ │ -{ok,"/usr/local/log"}

      The values in the .app file can be overridden by values in a system │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"/usr/local/log"}

    The values in the .app file can be overridden by values in a system │ │ │ configuration file. This is a file that contains configuration parameters for │ │ │ -relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ +relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │   ...,
    │ │ │ - {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ + {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ started with the command-line argument -config Name. For details, see │ │ │ config in Kernel.

    Example:

    A file test.config is created with the following contents:

    [{ch_app, [{file, "testlog"}]}].

    The value of file overrides the value of file as defined in the .app file:

    % erl -config test
    │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │  
    │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ -1> application:start(ch_app).
    │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ +1> application:start(ch_app).
    │ │ │  ok
    │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ -{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ configuration file is to be used and that file is to be called sys.config.

    The values in the .app file and the values in a system configuration file can │ │ │ be overridden directly from the command line:

    % erl -ApplName Par1 Val1 ... ParN ValN

    Example:

    % erl -ch_app file '"testlog"'
    │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │  
    │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ -1> application:start(ch_app).
    │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ +1> application:start(ch_app).
    │ │ │  ok
    │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ -{ok,"testlog"}

    │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"testlog"}

    │ │ │ │ │ │ │ │ │ │ │ │ Application Start Types │ │ │

    │ │ │

    A start type is defined when starting the application:

    application:start(Application, Type)

    application:start(Application) is the same as calling │ │ │ application:start(Application, temporary). The type can also be permanent or │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ @@ -120,18 +120,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Functional Module │ │ │ │ │ │

    When a functional module has been changed, for example, if a new function has │ │ │ been added or a bug has been corrected, simple code replacement is sufficient, │ │ │ -for example:

    {"2",
    │ │ │ - [{"1", [{load_module, m}]}],
    │ │ │ - [{"1", [{load_module, m}]}]
    │ │ │ -}.

    │ │ │ +for example:

    {"2",
    │ │ │ + [{"1", [{load_module, m}]}],
    │ │ │ + [{"1", [{load_module, m}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing a Residence Module │ │ │

    │ │ │

    In a system implemented according to the OTP design principles, all processes, │ │ │ except system processes and special processes, reside in one of the behaviours │ │ │ @@ -142,46 +142,46 @@ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Callback Module │ │ │ │ │ │

    A callback module is a functional module, and for code extensions simple code │ │ │ replacement is sufficient.

    Example

    When adding a function to ch3, as described in the example in │ │ │ -Release Handling, ch_app.appup looks as follows:

    {"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    OTP also supports changing the internal state of behaviour processes; see │ │ │ +Release Handling, ch_app.appup looks as follows:

    {"2",
    │ │ │ + [{"1", [{load_module, ch3}]}],
    │ │ │ + [{"1", [{load_module, ch3}]}]
    │ │ │ +}.

    OTP also supports changing the internal state of behaviour processes; see │ │ │ Changing Internal State.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Internal State │ │ │

    │ │ │

    In this case, simple code replacement is not sufficient. The process must │ │ │ explicitly transform its state using the callback function code_change/3 before │ │ │ switching to the new version of the callback module. Thus, synchronized code │ │ │ replacement is used.

    Example

    Consider the ch3 module from │ │ │ gen_server Behaviour. The internal state is a term │ │ │ Chs representing the available channels. Assume you want to add a counter N, │ │ │ which keeps track of the number of alloc requests so far. This means that the │ │ │ -format must be changed to {Chs,N}.

    The .appup file can look as follows:

    {"2",
    │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}],
    │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}]
    │ │ │ -}.

    The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ +format must be changed to {Chs,N}.

    The .appup file can look as follows:

    {"2",
    │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}],
    │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}]
    │ │ │ +}.

    The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ which says that the affected processes are to do a state transformation before │ │ │ loading the new version of the module. This is done by the processes calling the │ │ │ callback function code_change/3 (see gen_server in STDLIB). │ │ │ -The term Extra, in this case [], is passed as is to the function:

    -module(ch3).
    │ │ │ +The term Extra, in this case [], is passed as is to the function:

    -module(ch3).
    │ │ │  ...
    │ │ │ --export([code_change/3]).
    │ │ │ +-export([code_change/3]).
    │ │ │  ...
    │ │ │ -code_change({down, _Vsn}, {Chs, N}, _Extra) ->
    │ │ │ -    {ok, Chs};
    │ │ │ -code_change(_Vsn, Chs, _Extra) ->
    │ │ │ -    {ok, {Chs, 0}}.

    The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ +code_change({down, _Vsn}, {Chs, N}, _Extra) -> │ │ │ + {ok, Chs}; │ │ │ +code_change(_Vsn, Chs, _Extra) -> │ │ │ + {ok, {Chs, 0}}.

    The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ a upgrade. The term Vsn is fetched from the 'original' version of the module, │ │ │ that is, the version you are upgrading from, or downgrading to.

    The version is defined by the module attribute vsn, if any. There is no such │ │ │ attribute in ch3, so in this case the version is the checksum (a huge integer) │ │ │ of the beam file, an uninteresting value, which is ignored.

    The other callback functions of ch3 must also be modified and perhaps a new │ │ │ interface function must be added, but this is not shown here.

    │ │ │ │ │ │ │ │ │ @@ -190,67 +190,67 @@ │ │ │

    │ │ │

    Assume that a module is extended by adding an interface function, as in the │ │ │ example in Release Handling, where a function │ │ │ available/0 is added to ch3.

    If a call is added to this function, say in module m1, a runtime error could │ │ │ can occur during release upgrade if the new version of m1 is loaded first and │ │ │ calls ch3:available/0 before the new version of ch3 is loaded.

    Thus, ch3 must be loaded before m1, in the upgrade case, and conversely in │ │ │ the downgrade case. m1 is said to be dependent on ch3. In a release │ │ │ -handling instruction, this is expressed by the DepMods element:

    {load_module, Module, DepMods}
    │ │ │ -{update, Module, {advanced, Extra}, DepMods}

    DepMods is a list of modules, on which Module is dependent.

    Example

    The module m1 in application myapp is dependent on ch3 when │ │ │ +handling instruction, this is expressed by the DepMods element:

    {load_module, Module, DepMods}
    │ │ │ +{update, Module, {advanced, Extra}, DepMods}

    DepMods is a list of modules, on which Module is dependent.

    Example

    The module m1 in application myapp is dependent on ch3 when │ │ │ upgrading from "1" to "2", or downgrading from "2" to "1":

    myapp.appup:
    │ │ │  
    │ │ │ -{"2",
    │ │ │ - [{"1", [{load_module, m1, [ch3]}]}],
    │ │ │ - [{"1", [{load_module, m1, [ch3]}]}]
    │ │ │ -}.
    │ │ │ +{"2",
    │ │ │ + [{"1", [{load_module, m1, [ch3]}]}],
    │ │ │ + [{"1", [{load_module, m1, [ch3]}]}]
    │ │ │ +}.
    │ │ │  
    │ │ │  ch_app.appup:
    │ │ │  
    │ │ │ -{"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ -look as follows:

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{load_module, ch3},
    │ │ │ -    {load_module, m1, [ch3]}]}],
    │ │ │ - [{"1",
    │ │ │ -   [{load_module, ch3},
    │ │ │ -    {load_module, m1, [ch3]}]}]
    │ │ │ -}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ +{"2", │ │ │ + [{"1", [{load_module, ch3}]}], │ │ │ + [{"1", [{load_module, ch3}]}] │ │ │ +}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ +look as follows:

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{load_module, ch3},
    │ │ │ +    {load_module, m1, [ch3]}]}],
    │ │ │ + [{"1",
    │ │ │ +   [{load_module, ch3},
    │ │ │ +    {load_module, m1, [ch3]}]}]
    │ │ │ +}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ difference between up- and downgrading and generates a correct relup, where │ │ │ ch3 is loaded before m1 when upgrading, but m1 is loaded before ch3 when │ │ │ downgrading.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Code for a Special Process │ │ │

    │ │ │

    In this case, simple code replacement is not sufficient. When a new version of a │ │ │ residence module for a special process is loaded, the process must make a fully │ │ │ qualified call to its loop function to switch to the new code. Thus, │ │ │ synchronized code replacement must be used.

    Note

    The name(s) of the user-defined residence module(s) must be listed in the │ │ │ Modules part of the child specification for the special process. Otherwise │ │ │ the release handler cannot find the process.

    Example

    Consider the example ch4 in sys and proc_lib. │ │ │ -When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ - permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ +When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ + permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ to be loaded when upgrading from version "1" to "2" of this application, │ │ │ -sp_app.appup can look as follows:

    {"2",
    │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ -}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ +sp_app.appup can look as follows:

    {"2",
    │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ +}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ instruction makes the special process call the callback function │ │ │ system_code_change/4, a function the user must implement. The term Extra, in │ │ │ -this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ +this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │  ...
    │ │ │ --export([system_code_change/4]).
    │ │ │ +-export([system_code_change/4]).
    │ │ │  ...
    │ │ │  
    │ │ │ -system_code_change(Chs, _Module, _OldVsn, _Extra) ->
    │ │ │ -    {ok, Chs}.
    • The first argument is the internal state State, passed from │ │ │ +system_code_change(Chs, _Module, _OldVsn, _Extra) -> │ │ │ + {ok, Chs}.

    In this case, all arguments but the first are ignored and the function simply │ │ │ returns the internal state again. This is enough if the code only has been │ │ │ extended. If instead the internal state is changed (similar to the example in │ │ │ @@ -271,85 +271,85 @@ │ │ │ Changing Properties │ │ │ │ │ │

    Since the supervisor is to change its internal state, synchronized code │ │ │ replacement is required. However, a special update instruction must be used.

    First, the new version of the callback module must be loaded, both in the case │ │ │ of upgrade and downgrade. Then the new return value of init/1 can be checked │ │ │ and the internal state be changed accordingly.

    The following upgrade instruction is used for supervisors:

    {update, Module, supervisor}

    Example

    To change the restart strategy of ch_sup (from │ │ │ Supervisor Behaviour) from one_for_one to one_for_all, │ │ │ -change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ +change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │  ...
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ -}.

    │ │ │ +init(_Args) -> │ │ │ + {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Child Specifications │ │ │

    │ │ │

    The instruction, and thus the .appup file, when changing an existing child │ │ │ -specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ -}.

    The changes do not affect existing child processes. For example, changing the │ │ │ +specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ +}.

    The changes do not affect existing child processes. For example, changing the │ │ │ start function only specifies how the child process is to be restarted, if │ │ │ needed later on.

    The id of the child specification cannot be changed.

    Changing the Modules field of the child specification can affect the release │ │ │ handling process itself, as this field is used to identify which processes are │ │ │ affected when doing a synchronized code replacement.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding and Deleting Child Processes │ │ │

    │ │ │

    As stated earlier, changing child specifications does not affect existing child │ │ │ processes. New child specifications are automatically added, but not deleted. │ │ │ Child processes are not automatically started or terminated, this must be done │ │ │ using apply instructions.

    Example

    Assume a new child process m1 is to be added to ch_sup when │ │ │ upgrading ch_app from "1" to "2". This means m1 is to be deleted when │ │ │ -downgrading from "2" to "1":

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{update, ch_sup, supervisor},
    │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ -   ]}],
    │ │ │ - [{"1",
    │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ -    {update, ch_sup, supervisor}
    │ │ │ -   ]}]
    │ │ │ -}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ +downgrading from "2" to "1":

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{update, ch_sup, supervisor},
    │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ +   ]}],
    │ │ │ + [{"1",
    │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ +    {update, ch_sup, supervisor}
    │ │ │ +   ]}]
    │ │ │ +}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ supervisor is not registered, it cannot be accessed directly from the script. │ │ │ Instead a help function that finds the pid of the supervisor and calls │ │ │ supervisor:restart_child, and so on, must be written. This function is then to │ │ │ be called from the script using the apply instruction.

    If the module m1 is introduced in version "2" of ch_app, it must also be │ │ │ -loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{add_module, m1},
    │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ -   ]}],
    │ │ │ - [{"1",
    │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ -    {delete_module, m1}
    │ │ │ -   ]}]
    │ │ │ -}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ +loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{add_module, m1},
    │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ +   ]}],
    │ │ │ + [{"1",
    │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ +    {delete_module, m1}
    │ │ │ +   ]}]
    │ │ │ +}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ m1 must be loaded, and the supervisor child specification changed, before the │ │ │ new child process can be started. When downgrading, the child process must be │ │ │ terminated before the child specification is changed and the module is deleted.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding or Deleting a Module │ │ │

    │ │ │ -

    _Example

    _ A new functional module m is added to ch_app:

    {"2",
    │ │ │ - [{"1", [{add_module, m}]}],
    │ │ │ - [{"1", [{delete_module, m}]}]

    │ │ │ +

    _Example

    _ A new functional module m is added to ch_app:

    {"2",
    │ │ │ + [{"1", [{add_module, m}]}],
    │ │ │ + [{"1", [{delete_module, m}]}]

    │ │ │ │ │ │ │ │ │ │ │ │ Starting or Terminating a Process │ │ │

    │ │ │

    In a system structured according to the OTP design principles, any process would │ │ │ be a child process belonging to a supervisor, see │ │ │ @@ -369,29 +369,29 @@ │ │ │ Restarting an Application │ │ │ │ │ │

    Restarting an application is useful when a change is too complicated to be made │ │ │ without restarting the processes, for example, if the supervisor hierarchy has │ │ │ been restructured.

    Example

    When adding a child m1 to ch_sup, as in │ │ │ Adding and Deleting Child Processes in Changing a │ │ │ Supervisor, an alternative to updating the supervisor is to restart the entire │ │ │ -application:

    {"2",
    │ │ │ - [{"1", [{restart_application, ch_app}]}],
    │ │ │ - [{"1", [{restart_application, ch_app}]}]
    │ │ │ -}.

    │ │ │ +application:

    {"2",
    │ │ │ + [{"1", [{restart_application, ch_app}]}],
    │ │ │ + [{"1", [{restart_application, ch_app}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing an Application Specification │ │ │

    │ │ │

    When installing a release, the application specifications are automatically │ │ │ updated before evaluating the relup script. Thus, no instructions are needed │ │ │ -in the .appup file:

    {"2",
    │ │ │ - [{"1", []}],
    │ │ │ - [{"1", []}]
    │ │ │ -}.

    │ │ │ +in the .appup file:

    {"2",
    │ │ │ + [{"1", []}],
    │ │ │ + [{"1", []}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Application Configuration │ │ │

    │ │ │

    Changing an application configuration by updating the env key in the .app │ │ │ file is an instance of changing an application specification, see the previous │ │ │ @@ -406,26 +406,26 @@ │ │ │ applications apply to primary applications only. There are no corresponding │ │ │ instructions for included applications. However, since an included application │ │ │ is really a supervision tree with a topmost supervisor, started as a child │ │ │ process to a supervisor in the including application, a .relup file can be │ │ │ manually created.

    Example

    Assume there is a release containing an application prim_app, which │ │ │ have a supervisor prim_sup in its supervision tree.

    In a new version of the release, the application ch_app is to be included in │ │ │ prim_app. That is, its topmost supervisor ch_sup is to be started as a child │ │ │ -process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ -    {ok, {...supervisor flags...,
    │ │ │ -          [...,
    │ │ │ -           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ -            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ -           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ - [...,
    │ │ │ -  {vsn, "2"},
    │ │ │ +process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ +    {ok, {...supervisor flags...,
    │ │ │ +          [...,
    │ │ │ +           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ +            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ +           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ + [...,
    │ │ │ +  {vsn, "2"},
    │ │ │    ...,
    │ │ │ -  {included_applications, [ch_app]},
    │ │ │ +  {included_applications, [ch_app]},
    │ │ │    ...
    │ │ │ - ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ + ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │   ...,
    │ │ │   [...,
    │ │ │    {prim_app, "2"},
    │ │ │    {ch_app, "1"}]}.

    The included application can be started in two ways. This is described in the │ │ │ next two sections.

    │ │ │ │ │ │ │ │ │ @@ -480,74 +480,74 @@ │ │ │

    Step 4b) Another way to start the included application (or stop it in the case │ │ │ of downgrade) is by combining instructions for adding and removing child │ │ │ processes to/from prim_sup with instructions for loading/unloading all │ │ │ ch_app code and its application specification.

    Again, the .relup file is created manually, either from scratch or by editing a │ │ │ generated version. Load all code for ch_app first, and also load the │ │ │ application specification, before prim_sup is updated. When downgrading, │ │ │ prim_sup is to updated first, before the code for ch_app and its application │ │ │ -specification are unloaded.

    {"B",
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ -    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ +specification are unloaded.

    {"B",
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ +    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ -    {suspend,[prim_sup]},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {code_change,up,[{prim_sup,[]}]},
    │ │ │ -    {resume,[prim_sup]},
    │ │ │ -    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ +    {suspend,[prim_sup]},
    │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {code_change,up,[{prim_sup,[]}]},
    │ │ │ +    {resume,[prim_sup]},
    │ │ │ +    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}},
    │ │ │ -    {apply,{supervisor,delete_child,[prim_sup,ch_sup]}},
    │ │ │ -    {suspend,[prim_sup]},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {code_change,down,[{prim_sup,[]}]},
    │ │ │ -    {resume,[prim_sup]},
    │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ -    {apply,{application,unload,[ch_app]}}]}]
    │ │ │ -}.

    │ │ │ + {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, │ │ │ + {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, │ │ │ + {suspend,[prim_sup]}, │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ + {code_change,down,[{prim_sup,[]}]}, │ │ │ + {resume,[prim_sup]}, │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ + {apply,{application,unload,[ch_app]}}]}] │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Non-Erlang Code │ │ │

    │ │ │

    Changing code for a program written in another programming language than Erlang, │ │ │ for example, a port program, is application-dependent and OTP provides no │ │ │ special support.

    Example

    When changing code for a port program, assume that the Erlang process │ │ │ controlling the port is a gen_server portc and that the port is opened in │ │ │ -the callback function init/1:

    init(...) ->
    │ │ │ +the callback function init/1:

    init(...) ->
    │ │ │      ...,
    │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │      ...,
    │ │ │ -    {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ + {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ extended with a code_change/3 function, which closes the old port and opens a │ │ │ new port. (If necessary, the gen_server can first request data that must be │ │ │ -saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ +saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │      State#state.port ! close,
    │ │ │      receive
    │ │ │ -        {Port,close} ->
    │ │ │ +        {Port,close} ->
    │ │ │              true
    │ │ │      end,
    │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ -    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ -file:

    ["2",
    │ │ │ - [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ - [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ -].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ -the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ +    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ +file:

    ["2",
    │ │ │ + [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ + [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ +].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ +the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │  ...

    │ │ │ │ │ │ │ │ │ │ │ │ Runtime System Restart and Upgrade │ │ │

    │ │ │

    Two upgrade instructions restart the runtime system:

    • restart_new_emulator

      Intended when ERTS, Kernel, STDLIB, or SASL is upgraded. It is automatically │ │ │ @@ -555,22 +555,22 @@ │ │ │ executed before all other upgrade instructions. For more information about │ │ │ this instruction, see restart_new_emulator (Low-Level) in │ │ │ Release Handling Instructions.

    • restart_emulator

      Used when a restart of the runtime system is required after all other upgrade │ │ │ instructions are executed. For more information about this instruction, see │ │ │ restart_emulator (Low-Level) in │ │ │ Release Handling Instructions.

    If a runtime system restart is necessary and no upgrade instructions are needed, │ │ │ that is, if the restart itself is enough for the upgraded applications to start │ │ │ -running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [restart_emulator]}],
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [restart_emulator]}]
    │ │ │ -}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ +running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [restart_emulator]}],
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [restart_emulator]}]
    │ │ │ +}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ of release packages, automatic path updates, and so on, can be used without │ │ │ having to specify .appup files.

    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/benchmarking.html │ │ │ @@ -144,16 +144,16 @@ │ │ │ fast as possible, what can we do? One way could be to generate more │ │ │ than two bytes at the time.

    % erlperf 'rand:bytes(100).' 'crypto:strong_rand_bytes(100).'
    │ │ │  Code                                   ||        QPS       Time   Rel
    │ │ │  rand:bytes(100).                        1    2124 Ki     470 ns  100%
    │ │ │  crypto:strong_rand_bytes(100).          1    1915 Ki     522 ns   90%

    rand:bytes/1 is still faster when we generate 100 bytes at the time, │ │ │ but the relative difference is smaller.

    % erlperf 'rand:bytes(1000).' 'crypto:strong_rand_bytes(1000).'
    │ │ │  Code                                    ||        QPS       Time   Rel
    │ │ │ -crypto:strong_rand_bytes(1000).          1    1518 Ki     658 ns  100%
    │ │ │ -rand:bytes(1000).                        1     284 Ki    3521 ns   19%

    When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ +crypto:strong_rand_bytes(1000). 1 1518 Ki 658 ns 100% │ │ │ +rand:bytes(1000). 1 284 Ki 3521 ns 19%

    When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ now the fastest.

    │ │ │ │ │ │ │ │ │ │ │ │ Benchmarking using Erlang/OTP functionality │ │ │

    │ │ │

    Benchmarks can measure wall-clock time or CPU time.

    • timer:tc/3 measures wall-clock time. The advantage with wall-clock time is │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/binaryhandling.html │ │ │ @@ -114,43 +114,43 @@ │ │ │ │ │ │ Constructing and Matching Binaries │ │ │ │ │ │ │ │ │

      This section gives a few examples on how to handle binaries in an efficient way. │ │ │ The sections that follow take an in-depth look at how binaries are implemented │ │ │ and how to best take advantages of the optimizations done by the compiler and │ │ │ -runtime system.

      Binaries can be efficiently built in the following way:

      DO

      my_list_to_binary(List) ->
      │ │ │ -    my_list_to_binary(List, <<>>).
      │ │ │ +runtime system.

      Binaries can be efficiently built in the following way:

      DO

      my_list_to_binary(List) ->
      │ │ │ +    my_list_to_binary(List, <<>>).
      │ │ │  
      │ │ │ -my_list_to_binary([H|T], Acc) ->
      │ │ │ -    my_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ -my_list_to_binary([], Acc) ->
      │ │ │ +my_list_to_binary([H|T], Acc) ->
      │ │ │ +    my_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ +my_list_to_binary([], Acc) ->
      │ │ │      Acc.

      Appending data to a binary as in the example is efficient because it is │ │ │ specially optimized by the runtime system to avoid copying the Acc binary │ │ │ -every time.

      Prepending data to a binary in a loop is not efficient:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ -    rev_list_to_binary(List, <<>>).
      │ │ │ +every time.

      Prepending data to a binary in a loop is not efficient:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ +    rev_list_to_binary(List, <<>>).
      │ │ │  
      │ │ │ -rev_list_to_binary([H|T], Acc) ->
      │ │ │ -    rev_list_to_binary(T, <<H,Acc/binary>>);
      │ │ │ -rev_list_to_binary([], Acc) ->
      │ │ │ +rev_list_to_binary([H|T], Acc) ->
      │ │ │ +    rev_list_to_binary(T, <<H,Acc/binary>>);
      │ │ │ +rev_list_to_binary([], Acc) ->
      │ │ │      Acc.

      This is not efficient for long lists because the Acc binary is copied every │ │ │ -time. One way to make the function more efficient is like this:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ -    rev_list_to_binary(lists:reverse(List), <<>>).
      │ │ │ +time. One way to make the function more efficient is like this:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ +    rev_list_to_binary(lists:reverse(List), <<>>).
      │ │ │  
      │ │ │ -rev_list_to_binary([H|T], Acc) ->
      │ │ │ -    rev_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ -rev_list_to_binary([], Acc) ->
      │ │ │ -    Acc.

      Another way to avoid copying the binary each time is like this:

      DO

      rev_list_to_binary([H|T]) ->
      │ │ │ -    RevTail = rev_list_to_binary(T),
      │ │ │ -    <<RevTail/binary,H>>;
      │ │ │ -rev_list_to_binary([]) ->
      │ │ │ -    <<>>.

      Note that in each of the DO examples, the binary to be appended to is always │ │ │ -given as the first segment.

      Binaries can be efficiently matched in the following way:

      DO

      my_binary_to_list(<<H,T/binary>>) ->
      │ │ │ -    [H|my_binary_to_list(T)];
      │ │ │ -my_binary_to_list(<<>>) -> [].

      │ │ │ +rev_list_to_binary([H|T], Acc) -> │ │ │ + rev_list_to_binary(T, <<Acc/binary,H>>); │ │ │ +rev_list_to_binary([], Acc) -> │ │ │ + Acc.

      Another way to avoid copying the binary each time is like this:

      DO

      rev_list_to_binary([H|T]) ->
      │ │ │ +    RevTail = rev_list_to_binary(T),
      │ │ │ +    <<RevTail/binary,H>>;
      │ │ │ +rev_list_to_binary([]) ->
      │ │ │ +    <<>>.

      Note that in each of the DO examples, the binary to be appended to is always │ │ │ +given as the first segment.

      Binaries can be efficiently matched in the following way:

      DO

      my_binary_to_list(<<H,T/binary>>) ->
      │ │ │ +    [H|my_binary_to_list(T)];
      │ │ │ +my_binary_to_list(<<>>) -> [].

      │ │ │ │ │ │ │ │ │ │ │ │ How Binaries are Implemented │ │ │

      │ │ │

      Internally, binaries and bitstrings are implemented in the same way. In this │ │ │ section, they are called binaries because that is what they are called in the │ │ │ @@ -205,29 +205,29 @@ │ │ │ called referential transparency) of Erlang would break.

      │ │ │ │ │ │ │ │ │ │ │ │ Constructing Binaries │ │ │

      │ │ │

      Appending to a binary or bitstring in the following way is specially optimized │ │ │ -to avoid copying the binary:

      <<Binary/binary, ...>>
      │ │ │ +to avoid copying the binary:

      <<Binary/binary, ...>>
      │ │ │  %% - OR -
      │ │ │ -<<Binary/bitstring, ...>>

      This optimization is applied by the runtime system in a way that makes it │ │ │ +<<Binary/bitstring, ...>>

      This optimization is applied by the runtime system in a way that makes it │ │ │ effective in most circumstances (for exceptions, see │ │ │ Circumstances That Force Copying). The │ │ │ optimization in its basic form does not need any help from the compiler. │ │ │ However, the compiler add hints to the runtime system when it is safe to apply │ │ │ the optimization in a more efficient way.

      Change

      The compiler support for making the optimization more efficient was added in │ │ │ Erlang/OTP 26.

      To explain how the basic optimization works, let us examine the following code │ │ │ -line by line:

      Bin0 = <<0>>,                    %% 1
      │ │ │ -Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
      │ │ │ -Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
      │ │ │ -Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
      │ │ │ -Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
      │ │ │ -{Bin4,Bin3}                      %% 6
      • Line 1 (marked with the %% 1 comment), assigns a │ │ │ +line by line:

        Bin0 = <<0>>,                    %% 1
        │ │ │ +Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
        │ │ │ +Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
        │ │ │ +Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
        │ │ │ +Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
        │ │ │ +{Bin4,Bin3}                      %% 6
        • Line 1 (marked with the %% 1 comment), assigns a │ │ │ heap binary to the Bin0 variable.

        • Line 2 is an append operation. As Bin0 has not been involved in an append │ │ │ operation, a new refc binary is created and │ │ │ the contents of Bin0 is copied into it. The ProcBin part of the refc │ │ │ binary has its size set to the size of the data stored in the binary, while │ │ │ the binary object has extra space allocated. The size of the binary object is │ │ │ either twice the size of Bin1 or 256, whichever is larger. In this case it │ │ │ is 256.

        • Line 3 is more interesting. Bin1 has been used in an append operation, and │ │ │ @@ -253,23 +253,23 @@ │ │ │ handle an append operation to a heap binary by copying it to a refc binary (line │ │ │ 2), and also handle an append operation to a previous version of the binary by │ │ │ copying it (line 5). The support for doing that does not come for free. For │ │ │ example, to make it possible to know when it is necessary to copy the binary, │ │ │ for every append operation, the runtime system must create a sub binary.

          When the compiler can determine that none of those situations need to be handled │ │ │ and that the append operation cannot possibly fail, the compiler generates code │ │ │ that causes the runtime system to apply a more efficient variant of the │ │ │ -optimization.

          Example:

          -module(repack).
          │ │ │ --export([repack/1]).
          │ │ │ +optimization.

          Example:

          -module(repack).
          │ │ │ +-export([repack/1]).
          │ │ │  
          │ │ │ -repack(Bin) when is_binary(Bin) ->
          │ │ │ -    repack(Bin, <<>>).
          │ │ │ +repack(Bin) when is_binary(Bin) ->
          │ │ │ +    repack(Bin, <<>>).
          │ │ │  
          │ │ │ -repack(<<C:8,T/binary>>, Result) ->
          │ │ │ -    repack(T, <<Result/binary,C:16>>);
          │ │ │ -repack(<<>>, Result) ->
          │ │ │ +repack(<<C:8,T/binary>>, Result) ->
          │ │ │ +    repack(T, <<Result/binary,C:16>>);
          │ │ │ +repack(<<>>, Result) ->
          │ │ │      Result.

          The repack/2 function only keeps a single version of the binary, so there is │ │ │ never any need to copy the binary. The compiler rewrites the creation of the │ │ │ empty binary in repack/1 to instead create a refc binary with 256 bytes │ │ │ already reserved; thus, the append operation in repack/2 never needs to handle │ │ │ a binary not prepared for appending.

          │ │ │ │ │ │ │ │ │ @@ -281,72 +281,72 @@ │ │ │ reason is that the binary object can be moved (reallocated) during an append │ │ │ operation, and when that happens, the pointer in the ProcBin must be updated. If │ │ │ there would be more than one ProcBin pointing to the binary object, it would not │ │ │ be possible to find and update all of them.

          Therefore, certain operations on a binary mark it so that any future append │ │ │ operation will be forced to copy the binary. In most cases, the binary object │ │ │ will be shrunk at the same time to reclaim the extra space allocated for │ │ │ growing.

          When appending to a binary as follows, only the binary returned from the latest │ │ │ -append operation will support further cheap append operations:

          Bin = <<Bin0,...>>

          In the code fragment in the beginning of this section, appending to Bin will │ │ │ +append operation will support further cheap append operations:

          Bin = <<Bin0,...>>

          In the code fragment in the beginning of this section, appending to Bin will │ │ │ be cheap, while appending to Bin0 will force the creation of a new binary and │ │ │ copying of the contents of Bin0.

          If a binary is sent as a message to a process or port, the binary will be shrunk │ │ │ and any further append operation will copy the binary data into a new binary. │ │ │ For example, in the following code fragment Bin1 will be copied in the third │ │ │ -line:

          Bin1 = <<Bin0,...>>,
          │ │ │ +line:

          Bin1 = <<Bin0,...>>,
          │ │ │  PortOrPid ! Bin1,
          │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The same happens if you insert a binary into an Ets table, send it to a port │ │ │ +Bin = <<Bin1,...>> %% Bin1 will be COPIED

          The same happens if you insert a binary into an Ets table, send it to a port │ │ │ using erlang:port_command/2, or pass it to │ │ │ enif_inspect_binary in a NIF.

          Matching a binary will also cause it to shrink and the next append operation │ │ │ -will copy the binary data:

          Bin1 = <<Bin0,...>>,
          │ │ │ -<<X,Y,Z,T/binary>> = Bin1,
          │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The reason is that a match context contains a │ │ │ +will copy the binary data:

          Bin1 = <<Bin0,...>>,
          │ │ │ +<<X,Y,Z,T/binary>> = Bin1,
          │ │ │ +Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The reason is that a match context contains a │ │ │ direct pointer to the binary data.

          If a process simply keeps binaries (either in "loop data" or in the process │ │ │ dictionary), the garbage collector can eventually shrink the binaries. If only │ │ │ one such binary is kept, it will not be shrunk. If the process later appends to │ │ │ a binary that has been shrunk, the binary object will be reallocated to make │ │ │ place for the data to be appended.

          │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │

          │ │ │ -

          Let us revisit the example in the beginning of the previous section:

          DO

          my_binary_to_list(<<H,T/binary>>) ->
          │ │ │ -    [H|my_binary_to_list(T)];
          │ │ │ -my_binary_to_list(<<>>) -> [].

          The first time my_binary_to_list/1 is called, a │ │ │ +

          Let us revisit the example in the beginning of the previous section:

          DO

          my_binary_to_list(<<H,T/binary>>) ->
          │ │ │ +    [H|my_binary_to_list(T)];
          │ │ │ +my_binary_to_list(<<>>) -> [].

          The first time my_binary_to_list/1 is called, a │ │ │ match context is created. The match context │ │ │ points to the first byte of the binary. 1 byte is matched out and the match │ │ │ context is updated to point to the second byte in the binary.

          At this point it would make sense to create a │ │ │ sub binary, but in this particular example the │ │ │ compiler sees that there will soon be a call to a function (in this case, to │ │ │ my_binary_to_list/1 itself) that immediately will create a new match context │ │ │ and discard the sub binary.

          Therefore my_binary_to_list/1 calls itself with the match context instead of │ │ │ with a sub binary. The instruction that initializes the matching operation │ │ │ basically does nothing when it sees that it was passed a match context instead │ │ │ of a binary.

          When the end of the binary is reached and the second clause matches, the match │ │ │ context will simply be discarded (removed in the next garbage collection, as │ │ │ there is no longer any reference to it).

          To summarize, my_binary_to_list/1 only needs to create one match context and │ │ │ no sub binaries.

          Notice that the match context in my_binary_to_list/1 was discarded when the │ │ │ entire binary had been traversed. What happens if the iteration stops before it │ │ │ -has reached the end of the binary? Will the optimization still work?

          after_zero(<<0,T/binary>>) ->
          │ │ │ +has reached the end of the binary? Will the optimization still work?

          after_zero(<<0,T/binary>>) ->
          │ │ │      T;
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ -    after_zero(T);
          │ │ │ -after_zero(<<>>) ->
          │ │ │ -    <<>>.

          Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ +after_zero(<<_,T/binary>>) -> │ │ │ + after_zero(T); │ │ │ +after_zero(<<>>) -> │ │ │ + <<>>.

          Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ second clause:

          ...
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ -    after_zero(T);
          │ │ │ -...

          But it will generate code that builds a sub binary in the first clause:

          after_zero(<<0,T/binary>>) ->
          │ │ │ +after_zero(<<_,T/binary>>) ->
          │ │ │ +    after_zero(T);
          │ │ │ +...

          But it will generate code that builds a sub binary in the first clause:

          after_zero(<<0,T/binary>>) ->
          │ │ │      T;
          │ │ │  ...

          Therefore, after_zero/1 builds one match context and one sub binary (assuming │ │ │ -it is passed a binary that contains a zero byte).

          Code like the following will also be optimized:

          all_but_zeroes_to_list(Buffer, Acc, 0) ->
          │ │ │ -    {lists:reverse(Acc),Buffer};
          │ │ │ -all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
          │ │ │ -    all_but_zeroes_to_list(T, Acc, Remaining-1);
          │ │ │ -all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
          │ │ │ -    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

          The compiler removes building of sub binaries in the second and third clauses, │ │ │ +it is passed a binary that contains a zero byte).

          Code like the following will also be optimized:

          all_but_zeroes_to_list(Buffer, Acc, 0) ->
          │ │ │ +    {lists:reverse(Acc),Buffer};
          │ │ │ +all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
          │ │ │ +    all_but_zeroes_to_list(T, Acc, Remaining-1);
          │ │ │ +all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
          │ │ │ +    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

          The compiler removes building of sub binaries in the second and third clauses, │ │ │ and it adds an instruction to the first clause that converts Buffer from a │ │ │ match context to a sub binary (or do nothing if Buffer is a binary already).

          But in more complicated code, how can one know whether the optimization is │ │ │ applied or not?

          │ │ │ │ │ │ │ │ │ │ │ │ Option bin_opt_info │ │ │ @@ -354,38 +354,38 @@ │ │ │

          Use the bin_opt_info option to have the compiler print a lot of information │ │ │ about binary optimizations. It can be given either to the compiler or erlc:

          erlc +bin_opt_info Mod.erl

          or passed through an environment variable:

          export ERL_COMPILER_OPTIONS=bin_opt_info

          Notice that the bin_opt_info is not meant to be a permanent option added to │ │ │ your Makefiles, because all messages that it generates cannot be eliminated. │ │ │ Therefore, passing the option through the environment is in most cases the most │ │ │ practical approach.

          The warnings look as follows:

          ./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: binary is returned from the function
          │ │ │  ./efficiency_guide.erl:62: Warning: OPTIMIZED: match context reused

          To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ -example:

          after_zero(<<0,T/binary>>) ->
          │ │ │ +example:

          after_zero(<<0,T/binary>>) ->
          │ │ │           %% BINARY CREATED: binary is returned from the function
          │ │ │      T;
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ +after_zero(<<_,T/binary>>) ->
          │ │ │           %% OPTIMIZED: match context reused
          │ │ │ -    after_zero(T);
          │ │ │ -after_zero(<<>>) ->
          │ │ │ -    <<>>.

          The warning for the first clause says that the creation of a sub binary cannot │ │ │ + after_zero(T); │ │ │ +after_zero(<<>>) -> │ │ │ + <<>>.

          The warning for the first clause says that the creation of a sub binary cannot │ │ │ be delayed, because it will be returned. The warning for the second clause says │ │ │ that a sub binary will not be created (yet).

          │ │ │ │ │ │ │ │ │ │ │ │ Unused Variables │ │ │

          │ │ │

          The compiler figures out if a variable is unused. The same code is generated for │ │ │ -each of the following functions:

          count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
          │ │ │ -count1(<<>>, Count) -> Count.
          │ │ │ +each of the following functions:

          count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
          │ │ │ +count1(<<>>, Count) -> Count.
          │ │ │  
          │ │ │ -count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
          │ │ │ -count2(<<>>, Count) -> Count.
          │ │ │ +count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
          │ │ │ +count2(<<>>, Count) -> Count.
          │ │ │  
          │ │ │ -count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
          │ │ │ -count3(<<>>, Count) -> Count.

          In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ +count3(<<_H,T/binary>>, Count) -> count3(T, Count+1); │ │ │ +count3(<<>>, Count) -> Count.

          In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ out.

          │ │ │ │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Introduction │ │ │ │ │ │

          The complete specification for the bit syntax appears in the │ │ │ Reference Manual.

          In Erlang, a Bin is used for constructing binaries and matching binary patterns. │ │ │ -A Bin is written with the following syntax:

          <<E1, E2, ... En>>

          A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ -enable construction of binaries:

          Bin = <<E1, E2, ... En>>

          All elements must be bound. Or match a binary:

          <<E1, E2, ... En>> = Bin

          Here, Bin is bound and the elements are bound or unbound, as in any match.

          A Bin does not need to consist of a whole number of bytes.

          A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ +A Bin is written with the following syntax:

          <<E1, E2, ... En>>

          A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ +enable construction of binaries:

          Bin = <<E1, E2, ... En>>

          All elements must be bound. Or match a binary:

          <<E1, E2, ... En>> = Bin

          Here, Bin is bound and the elements are bound or unbound, as in any match.

          A Bin does not need to consist of a whole number of bytes.

          A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ not need to be divisible by 8. If the number of bits is divisible by 8, the │ │ │ bitstring is also a binary.

          Each element specifies a certain segment of the bitstring. A segment is a set │ │ │ of contiguous bits of the binary (not necessarily on a byte boundary). The first │ │ │ element specifies the initial segment, the second element specifies the │ │ │ following segment, and so on.

          The following examples illustrate how binaries are constructed, or matched, and │ │ │ how elements and tails are specified.

          │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

          │ │ │

          Example 1: A binary can be constructed from a set of constants or a string │ │ │ -literal:

          Bin11 = <<1, 17, 42>>,
          │ │ │ -Bin12 = <<"abc">>

          This gives two binaries of size 3, with the following evaluations:

          Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ +literal:

          Bin11 = <<1, 17, 42>>,
          │ │ │ +Bin12 = <<"abc">>

          This gives two binaries of size 3, with the following evaluations:

          Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ variables:

          A = 1, B = 17, C = 42,
          │ │ │ -Bin2 = <<A, B, C:16>>

          This gives a binary of size 4. Here, a size expression is used for the │ │ │ +Bin2 = <<A, B, C:16>>

          This gives a binary of size 4. Here, a size expression is used for the │ │ │ variable C to specify a 16-bits segment of Bin2.

          binary_to_list(Bin2) evaluates to [1, 17, 00, 42].

          Example 3: A Bin can also be used for matching. D, E, and F are unbound │ │ │ -variables, and Bin2 is bound, as in Example 2:

          <<D:16, E, F/binary>> = Bin2

          This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ +variables, and Bin2 is bound, as in Example 2:

          <<D:16, E, F/binary>> = Bin2

          This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ binary_to_list(F) = [42].

          Example 4: The following is a more elaborate example of matching. Here, │ │ │ Dgram is bound to the consecutive bytes of an IP datagram of IP protocol │ │ │ -version 4. The ambition is to extract the header and the data of the datagram:

          -define(IP_VERSION, 4).
          │ │ │ --define(IP_MIN_HDR_LEN, 5).
          │ │ │ +version 4. The ambition is to extract the header and the data of the datagram:

          -define(IP_VERSION, 4).
          │ │ │ +-define(IP_MIN_HDR_LEN, 5).
          │ │ │  
          │ │ │ -DgramSize = byte_size(Dgram),
          │ │ │ +DgramSize = byte_size(Dgram),
          │ │ │  case Dgram of
          │ │ │ -    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
          │ │ │ +    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
          │ │ │        ID:16, Flgs:3, FragOff:13,
          │ │ │        TTL:8, Proto:8, HdrChkSum:16,
          │ │ │        SrcIP:32,
          │ │ │ -      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
          │ │ │ -        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
          │ │ │ -        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
          │ │ │ +      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
          │ │ │ +        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
          │ │ │ +        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
          │ │ │      ...
          │ │ │  end.

          Here, the segment corresponding to the Opts variable has a type modifier, │ │ │ specifying that Opts is to bind to a binary. All other variables have the │ │ │ default type equal to unsigned integer.

          An IP datagram header is of variable length. This length is measured in the │ │ │ number of 32-bit words and is given in the segment corresponding to HLen. The │ │ │ minimum value of HLen is 5. It is the segment corresponding to Opts that is │ │ │ variable, so if HLen is equal to 5, Opts becomes an empty binary.

          The tail variables RestDgram and Data bind to binaries, as all tail │ │ │ @@ -218,80 +218,80 @@ │ │ │

          This section describes the rules for constructing binaries using the bit syntax. │ │ │ Unlike when constructing lists or tuples, the construction of a binary can fail │ │ │ with a badarg exception.

          There can be zero or more segments in a binary to be constructed. The expression │ │ │ <<>> constructs a zero length binary.

          Each segment in a binary can consist of zero or more bits. There are no │ │ │ alignment rules for individual segments of type integer and float. For │ │ │ binaries and bitstrings without size, the unit specifies the alignment. Since │ │ │ the default alignment for the binary type is 8, the size of a binary segment │ │ │ -must be a multiple of 8 bits, that is, only whole bytes.

          Example:

          <<Bin/binary,Bitstring/bitstring>>

          The variable Bin must contain a whole number of bytes, because the binary │ │ │ +must be a multiple of 8 bits, that is, only whole bytes.

          Example:

          <<Bin/binary,Bitstring/bitstring>>

          The variable Bin must contain a whole number of bytes, because the binary │ │ │ type defaults to unit:8. A badarg exception is generated if Bin consist │ │ │ of, for example, 17 bits.

          The Bitstring variable can consist of any number of bits, for example, 0, 1, │ │ │ 8, 11, 17, 42, and so on. This is because the default unit for bitstrings │ │ │ is 1.

          For clarity, it is recommended not to change the unit size for binaries. │ │ │ Instead, use binary when you need byte alignment and bitstring when you need │ │ │ bit alignment.

          The following example successfully constructs a bitstring of 7 bits, provided │ │ │ -that all of X and Y are integers:

          <<X:1,Y:6>>

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When constructing binaries, Value and Size can be any Erlang expression. │ │ │ +that all of X and Y are integers:

          <<X:1,Y:6>>

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When constructing binaries, Value and Size can be any Erlang expression. │ │ │ However, for syntactical reasons, both Value and Size must be enclosed in │ │ │ parenthesis if the expression consists of anything more than a single literal or │ │ │ -a variable. The following gives a compiler syntax error:

          <<X+1:8>>

          This expression must be rewritten into the following, to be accepted by the │ │ │ -compiler:

          <<(X+1):8>>

          │ │ │ +a variable. The following gives a compiler syntax error:

          <<X+1:8>>

          This expression must be rewritten into the following, to be accepted by the │ │ │ +compiler:

          <<(X+1):8>>

          │ │ │ │ │ │ │ │ │ │ │ │ Including Literal Strings │ │ │

          │ │ │ -

          A literal string can be written instead of an element:

          <<"hello">>

          This is syntactic sugar for the following:

          <<$h,$e,$l,$l,$o>>

          │ │ │ +

          A literal string can be written instead of an element:

          <<"hello">>

          This is syntactic sugar for the following:

          <<$h,$e,$l,$l,$o>>

          │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │

          │ │ │

          This section describes the rules for matching binaries, using the bit syntax.

          There can be zero or more segments in a binary pattern. A binary pattern can │ │ │ occur wherever patterns are allowed, including inside other patterns. Binary │ │ │ patterns cannot be nested. The pattern <<>> matches a zero length binary.

          Each segment in a binary can consist of zero or more bits. A segment of type │ │ │ binary must have a size evenly divisible by 8 (or divisible by the unit size, │ │ │ if the unit size has been changed). A segment of type bitstring has no │ │ │ restrictions on the size. A segment of type float must have size 64 or 32.

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When matching Value, value must be either a variable or an integer, or a │ │ │ floating point literal. Expressions are not allowed.

          Size must be a │ │ │ guard expression, which can use │ │ │ -literals and previously bound variables. The following is not allowed:

          foo(N, <<X:N,T/binary>>) ->
          │ │ │ -   {X,T}.

          The two occurrences of N are not related. The compiler will complain that the │ │ │ -N in the size field is unbound.

          The correct way to write this example is as follows:

          foo(N, Bin) ->
          │ │ │ -   <<X:N,T/binary>> = Bin,
          │ │ │ -   {X,T}.

          Note

          Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ +literals and previously bound variables. The following is not allowed:

          foo(N, <<X:N,T/binary>>) ->
          │ │ │ +   {X,T}.

          The two occurrences of N are not related. The compiler will complain that the │ │ │ +N in the size field is unbound.

          The correct way to write this example is as follows:

          foo(N, Bin) ->
          │ │ │ +   <<X:N,T/binary>> = Bin,
          │ │ │ +   {X,T}.

          Note

          Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ an integer.

          │ │ │ │ │ │ │ │ │ │ │ │ Binding and Using a Size Variable │ │ │

          │ │ │

          There is one exception to the rule that a variable that is used as size must be │ │ │ previously bound. It is possible to match and bind a variable, and use it as a │ │ │ -size within the same binary pattern. For example:

          bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
          │ │ │ -   {Payload,Rest}.

          Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ -used at the number of bytes to match out as a binary.

          Starting in OTP 23, the size can be a guard expression:

          bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
          │ │ │ -   {Payload,Rest}.

          Here Sz is the combined size of the header and the payload, so we will need to │ │ │ +size within the same binary pattern. For example:

          bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
          │ │ │ +   {Payload,Rest}.

          Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ +used at the number of bytes to match out as a binary.

          Starting in OTP 23, the size can be a guard expression:

          bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
          │ │ │ +   {Payload,Rest}.

          Here Sz is the combined size of the header and the payload, so we will need to │ │ │ subtract one byte to get the size of the payload.

          │ │ │ │ │ │ │ │ │ │ │ │ Getting the Rest of the Binary or Bitstring │ │ │

          │ │ │ -

          To match out the rest of a binary, specify a binary field without size:

          foo(<<A:8,Rest/binary>>) ->

          The size of the tail must be evenly divisible by 8.

          To match out the rest of a bitstring, specify a field without size:

          foo(<<A:8,Rest/bitstring>>) ->

          There are no restrictions on the number of bits in the tail.

          │ │ │ +

          To match out the rest of a binary, specify a binary field without size:

          foo(<<A:8,Rest/binary>>) ->

          The size of the tail must be evenly divisible by 8.

          To match out the rest of a bitstring, specify a field without size:

          foo(<<A:8,Rest/bitstring>>) ->

          There are no restrictions on the number of bits in the tail.

          │ │ │ │ │ │ │ │ │ │ │ │ Appending to a Binary │ │ │

          │ │ │ -

          Appending to a binary in an efficient way can be done as follows:

          triples_to_bin(T) ->
          │ │ │ -    triples_to_bin(T, <<>>).
          │ │ │ +

          Appending to a binary in an efficient way can be done as follows:

          triples_to_bin(T) ->
          │ │ │ +    triples_to_bin(T, <<>>).
          │ │ │  
          │ │ │ -triples_to_bin([{X,Y,Z} | T], Acc) ->
          │ │ │ -    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
          │ │ │ -triples_to_bin([], Acc) ->
          │ │ │ +triples_to_bin([{X,Y,Z} | T], Acc) ->
          │ │ │ +    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
          │ │ │ +triples_to_bin([], Acc) ->
          │ │ │      Acc.
          │ │ │ │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          open_port/2 with │ │ │ {spawn,ExtPrg} as the first argument. The string ExtPrg is the name of the │ │ │ external program, including any command line arguments. The second argument is a │ │ │ list of options, in this case only {packet,2}. This option says that a 2 byte │ │ │ length indicator is to be used to simplify the communication between C and │ │ │ Erlang. The Erlang port automatically adds the length indicator, but this must │ │ │ be done explicitly in the external C program.

          The process is also set to trap exits, which enables detection of failure of the │ │ │ -external program:

          -module(complex1).
          │ │ │ --export([start/1, init/1]).
          │ │ │ +external program:

          -module(complex1).
          │ │ │ +-export([start/1, init/1]).
          │ │ │  
          │ │ │ -start(ExtPrg) ->
          │ │ │ -  spawn(?MODULE, init, [ExtPrg]).
          │ │ │ +start(ExtPrg) ->
          │ │ │ +  spawn(?MODULE, init, [ExtPrg]).
          │ │ │  
          │ │ │ -init(ExtPrg) ->
          │ │ │ -  register(complex, self()),
          │ │ │ -  process_flag(trap_exit, true),
          │ │ │ -  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ -  loop(Port).

          Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ -message to the complex process and receive the following replies:

          foo(X) ->
          │ │ │ -  call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -  call_port({bar, Y}).
          │ │ │ +init(ExtPrg) ->
          │ │ │ +  register(complex, self()),
          │ │ │ +  process_flag(trap_exit, true),
          │ │ │ +  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ +  loop(Port).

          Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ +message to the complex process and receive the following replies:

          foo(X) ->
          │ │ │ +  call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +  call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -  complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +  complex ! {call, self(), Msg},
          │ │ │    receive
          │ │ │ -    {complex, Result} ->
          │ │ │ +    {complex, Result} ->
          │ │ │        Result
          │ │ │ -  end.

          The complex process does the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │ +  end.

          The complex process does the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │    receive
          │ │ │ -    {call, Caller, Msg} ->
          │ │ │ -      Port ! {self(), {command, encode(Msg)}},
          │ │ │ +    {call, Caller, Msg} ->
          │ │ │ +      Port ! {self(), {command, encode(Msg)}},
          │ │ │        receive
          │ │ │ -        {Port, {data, Data}} ->
          │ │ │ -          Caller ! {complex, decode(Data)}
          │ │ │ +        {Port, {data, Data}} ->
          │ │ │ +          Caller ! {complex, decode(Data)}
          │ │ │        end,
          │ │ │ -      loop(Port)
          │ │ │ +      loop(Port)
          │ │ │    end.

          Assuming that both the arguments and the results from the C functions are less │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ -represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          The resulting Erlang program, including functionality for stopping the port and │ │ │ -detecting port failures, is as follows:

          -module(complex1).
          │ │ │ --export([start/1, stop/0, init/1]).
          │ │ │ --export([foo/1, bar/1]).
          │ │ │ -
          │ │ │ -start(ExtPrg) ->
          │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
          │ │ │ -stop() ->
          │ │ │ +decode([Int]) -> Int.

          The resulting Erlang program, including functionality for stopping the port and │ │ │ +detecting port failures, is as follows:

          -module(complex1).
          │ │ │ +-export([start/1, stop/0, init/1]).
          │ │ │ +-export([foo/1, bar/1]).
          │ │ │ +
          │ │ │ +start(ExtPrg) ->
          │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
          │ │ │ +stop() ->
          │ │ │      complex ! stop.
          │ │ │  
          │ │ │ -foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -	{complex, Result} ->
          │ │ │ +	{complex, Result} ->
          │ │ │  	    Result
          │ │ │      end.
          │ │ │  
          │ │ │ -init(ExtPrg) ->
          │ │ │ -    register(complex, self()),
          │ │ │ -    process_flag(trap_exit, true),
          │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ -    loop(Port).
          │ │ │ +init(ExtPrg) ->
          │ │ │ +    register(complex, self()),
          │ │ │ +    process_flag(trap_exit, true),
          │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ +    loop(Port).
          │ │ │  
          │ │ │ -loop(Port) ->
          │ │ │ +loop(Port) ->
          │ │ │      receive
          │ │ │ -	{call, Caller, Msg} ->
          │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
          │ │ │ +	{call, Caller, Msg} ->
          │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
          │ │ │  	    receive
          │ │ │ -		{Port, {data, Data}} ->
          │ │ │ -		    Caller ! {complex, decode(Data)}
          │ │ │ +		{Port, {data, Data}} ->
          │ │ │ +		    Caller ! {complex, decode(Data)}
          │ │ │  	    end,
          │ │ │ -	    loop(Port);
          │ │ │ +	    loop(Port);
          │ │ │  	stop ->
          │ │ │ -	    Port ! {self(), close},
          │ │ │ +	    Port ! {self(), close},
          │ │ │  	    receive
          │ │ │ -		{Port, closed} ->
          │ │ │ -		    exit(normal)
          │ │ │ +		{Port, closed} ->
          │ │ │ +		    exit(normal)
          │ │ │  	    end;
          │ │ │ -	{'EXIT', Port, Reason} ->
          │ │ │ -	    exit(port_terminated)
          │ │ │ +	{'EXIT', Port, Reason} ->
          │ │ │ +	    exit(port_terminated)
          │ │ │      end.
          │ │ │  
          │ │ │ -encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          │ │ │ +decode([Int]) -> Int.

          │ │ │ │ │ │ │ │ │ │ │ │ C Program │ │ │

          │ │ │

          On the C side, it is necessary to write functions for receiving and sending data │ │ │ with 2 byte length indicators from/to Erlang. By default, the C program is to │ │ │ @@ -333,25 +333,25 @@ │ │ │ and terminates.

          │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

          │ │ │

          Step 1. Compile the C code:

          $ gcc -o extprg complex.c erl_comm.c port.c

          Step 2. Start Erlang and compile the Erlang code:

          $ erl
          │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -1> c(complex1).
          │ │ │ -{ok,complex1}

          Step 3. Run the example:

          2> complex1:start("./extprg").
          │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ +1> c(complex1).
          │ │ │ +{ok,complex1}

          Step 3. Run the example:

          2> complex1:start("./extprg").
          │ │ │  <0.34.0>
          │ │ │ -3> complex1:foo(3).
          │ │ │ +3> complex1:foo(3).
          │ │ │  4
          │ │ │ -4> complex1:bar(5).
          │ │ │ +4> complex1:bar(5).
          │ │ │  10
          │ │ │ -5> complex1:stop().
          │ │ │ +5> complex1:stop().
          │ │ │  stop
          │ │ │
          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          erl_ddll:load_driver/2, with the name of the shared library as │ │ │ argument.

          The port is then created using the BIF open_port/2, with the │ │ │ tuple {spawn, DriverName} as the first argument. The string SharedLib is the │ │ │ name of the port driver. The second argument is a list of options, none in this │ │ │ -case:

          -module(complex5).
          │ │ │ --export([start/1, init/1]).
          │ │ │ +case:

          -module(complex5).
          │ │ │ +-export([start/1, init/1]).
          │ │ │  
          │ │ │ -start(SharedLib) ->
          │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │ +start(SharedLib) ->
          │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │          ok -> ok;
          │ │ │ -        {error, already_loaded} -> ok;
          │ │ │ -        _ -> exit({error, could_not_load_driver})
          │ │ │ +        {error, already_loaded} -> ok;
          │ │ │ +        _ -> exit({error, could_not_load_driver})
          │ │ │      end,
          │ │ │ -    spawn(?MODULE, init, [SharedLib]).
          │ │ │ +    spawn(?MODULE, init, [SharedLib]).
          │ │ │  
          │ │ │ -init(SharedLib) ->
          │ │ │ -  register(complex, self()),
          │ │ │ -  Port = open_port({spawn, SharedLib}, []),
          │ │ │ -  loop(Port).

          Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ -message to the complex process and receive the following reply:

          foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +init(SharedLib) ->
          │ │ │ +  register(complex, self()),
          │ │ │ +  Port = open_port({spawn, SharedLib}, []),
          │ │ │ +  loop(Port).

          Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ +message to the complex process and receive the following reply:

          foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -        {complex, Result} ->
          │ │ │ +        {complex, Result} ->
          │ │ │              Result
          │ │ │ -    end.

          The complex process performs the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │ +    end.

          The complex process performs the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │      receive
          │ │ │ -        {call, Caller, Msg} ->
          │ │ │ -            Port ! {self(), {command, encode(Msg)}},
          │ │ │ +        {call, Caller, Msg} ->
          │ │ │ +            Port ! {self(), {command, encode(Msg)}},
          │ │ │              receive
          │ │ │ -                {Port, {data, Data}} ->
          │ │ │ -                    Caller ! {complex, decode(Data)}
          │ │ │ +                {Port, {data, Data}} ->
          │ │ │ +                    Caller ! {complex, decode(Data)}
          │ │ │              end,
          │ │ │ -            loop(Port)
          │ │ │ +            loop(Port)
          │ │ │      end.

          Assuming that both the arguments and the results from the C functions are less │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ -represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          The resulting Erlang program, including functions for stopping the port and │ │ │ +decode([Int]) -> Int.

          The resulting Erlang program, including functions for stopping the port and │ │ │ detecting port failures, is as follows:

          
          │ │ │ --module(complex5).
          │ │ │ --export([start/1, stop/0, init/1]).
          │ │ │ --export([foo/1, bar/1]).
          │ │ │ +-module(complex5).
          │ │ │ +-export([start/1, stop/0, init/1]).
          │ │ │ +-export([foo/1, bar/1]).
          │ │ │  
          │ │ │ -start(SharedLib) ->
          │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │ +start(SharedLib) ->
          │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │  	ok -> ok;
          │ │ │ -	{error, already_loaded} -> ok;
          │ │ │ -	_ -> exit({error, could_not_load_driver})
          │ │ │ +	{error, already_loaded} -> ok;
          │ │ │ +	_ -> exit({error, could_not_load_driver})
          │ │ │      end,
          │ │ │ -    spawn(?MODULE, init, [SharedLib]).
          │ │ │ +    spawn(?MODULE, init, [SharedLib]).
          │ │ │  
          │ │ │ -init(SharedLib) ->
          │ │ │ -    register(complex, self()),
          │ │ │ -    Port = open_port({spawn, SharedLib}, []),
          │ │ │ -    loop(Port).
          │ │ │ +init(SharedLib) ->
          │ │ │ +    register(complex, self()),
          │ │ │ +    Port = open_port({spawn, SharedLib}, []),
          │ │ │ +    loop(Port).
          │ │ │  
          │ │ │ -stop() ->
          │ │ │ +stop() ->
          │ │ │      complex ! stop.
          │ │ │  
          │ │ │ -foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -	{complex, Result} ->
          │ │ │ +	{complex, Result} ->
          │ │ │  	    Result
          │ │ │      end.
          │ │ │  
          │ │ │ -loop(Port) ->
          │ │ │ +loop(Port) ->
          │ │ │      receive
          │ │ │ -	{call, Caller, Msg} ->
          │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
          │ │ │ +	{call, Caller, Msg} ->
          │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
          │ │ │  	    receive
          │ │ │ -		{Port, {data, Data}} ->
          │ │ │ -		    Caller ! {complex, decode(Data)}
          │ │ │ +		{Port, {data, Data}} ->
          │ │ │ +		    Caller ! {complex, decode(Data)}
          │ │ │  	    end,
          │ │ │ -	    loop(Port);
          │ │ │ +	    loop(Port);
          │ │ │  	stop ->
          │ │ │ -	    Port ! {self(), close},
          │ │ │ +	    Port ! {self(), close},
          │ │ │  	    receive
          │ │ │ -		{Port, closed} ->
          │ │ │ -		    exit(normal)
          │ │ │ +		{Port, closed} ->
          │ │ │ +		    exit(normal)
          │ │ │  	    end;
          │ │ │ -	{'EXIT', Port, Reason} ->
          │ │ │ -	    io:format("~p ~n", [Reason]),
          │ │ │ -	    exit(port_terminated)
          │ │ │ +	{'EXIT', Port, Reason} ->
          │ │ │ +	    io:format("~p ~n", [Reason]),
          │ │ │ +	    exit(port_terminated)
          │ │ │      end.
          │ │ │  
          │ │ │ -encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          │ │ │ +decode([Int]) -> Int.

          │ │ │ │ │ │ │ │ │ │ │ │ C Driver │ │ │

          │ │ │

          The C driver is a module that is compiled and linked into a shared library. It │ │ │ uses a driver structure and includes the header file erl_driver.h.

          The driver structure is filled with the driver name and function pointers. It is │ │ │ @@ -347,25 +347,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │ │ │

          Step 1. Compile the C code:

          unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
          │ │ │  windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c

          Step 2. Start Erlang and compile the Erlang code:

          > erl
          │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -1> c(complex5).
          │ │ │ -{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ +1> c(complex5).
          │ │ │ +{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │  <0.34.0>
          │ │ │ -3> complex5:foo(3).
          │ │ │ +3> complex5:foo(3).
          │ │ │  4
          │ │ │ -4> complex5:bar(5).
          │ │ │ +4> complex5:bar(5).
          │ │ │  10
          │ │ │ -5> complex5:stop().
          │ │ │ +5> complex5:stop().
          │ │ │  stop
          │ │ │
          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ Compilation │ │ │ │ │ │

          Erlang programs must be compiled to object code. The compiler can generate a │ │ │ new file that contains the object code. The current abstract machine, which runs │ │ │ the object code, is called BEAM, therefore the object files get the suffix │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ -compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ +compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ loads Module.

          There is also a module make, which provides a set of functions similar to the │ │ │ UNIX type Make functions, see module make in Tools.

          The compiler can also be accessed from the OS prompt using the │ │ │ erl executable in ERTS.

          % erl -compile Module1...ModuleN
          │ │ │  % erl -make

          The erlc program provides way to compile modules from the OS │ │ │ shell, see the erlc executable in ERTS. It │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ paths for include files, and more.

          % erlc <flags> File1.erl...FileN.erl

          │ │ │ @@ -156,54 +156,54 @@ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

          Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ evaluated because of processes lingering in the old code.

          If a third instance of the module is loaded, the code server removes (purges) │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

          To change from old code to current code, a process must make a fully qualified │ │ │ -function call.

          Example:

          -module(m).
          │ │ │ --export([loop/0]).
          │ │ │ +function call.

          Example:

          -module(m).
          │ │ │ +-export([loop/0]).
          │ │ │  
          │ │ │ -loop() ->
          │ │ │ +loop() ->
          │ │ │      receive
          │ │ │          code_switch ->
          │ │ │ -            m:loop();
          │ │ │ +            m:loop();
          │ │ │          Msg ->
          │ │ │              ...
          │ │ │ -            loop()
          │ │ │ +            loop()
          │ │ │      end.

          To make the process change code, send the message code_switch to it. The │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ code. Notice that m:loop/0 must be exported.

          For code replacement of funs to work, use the syntax │ │ │ fun Module:FunctionName/Arity.

          │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │

          │ │ │

          The -on_load() directive names a function that is to be run automatically when │ │ │ -a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ +a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ process (which terminates as soon as the function returns).

          The function must return ok if the module is to become the new current code │ │ │ for the module and become callable.

          Returning any other value or generating an exception causes the new code to be │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ the error logger.

          If there already is current code for the module, that code will remain current │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ current code for a module, any process that makes an external call to the module │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ function have finished.

          Change

          Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ code would become old, essentially leaving the system without any working and │ │ │ reachable instance of the module.

          In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ -ok.

          Example:

          -module(m).
          │ │ │ --on_load(load_my_nifs/0).
          │ │ │ +ok.

          Example:

          -module(m).
          │ │ │ +-on_load(load_my_nifs/0).
          │ │ │  
          │ │ │ -load_my_nifs() ->
          │ │ │ +load_my_nifs() ->
          │ │ │      NifPath = ...,    %Set up the path to the NIF library.
          │ │ │      Info = ...,       %Initialize the Info term
          │ │ │ -    erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ + erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ report is sent to the error loader.

          │ │ │

          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          │ │ │ │ │ │ │ │ │ Operator ++ │ │ │

          │ │ │

          The ++ operator copies its left-hand side operand. That is clearly │ │ │ -seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ -    [H|my_plus_plus(T, Tail)];
          │ │ │ -my_plus_plus([], Tail) ->
          │ │ │ -    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ -    naive_reverse(T) ++ [H];
          │ │ │ -naive_reverse([]) ->
          │ │ │ -    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ -result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ -    naive_but_ok_reverse(List, []).
          │ │ │ +seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ +    [H|my_plus_plus(T, Tail)];
          │ │ │ +my_plus_plus([], Tail) ->
          │ │ │ +    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ +    naive_reverse(T) ++ [H];
          │ │ │ +naive_reverse([]) ->
          │ │ │ +    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ +result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ +    naive_but_ok_reverse(List, []).
          │ │ │  
          │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ -naive_but_ok_reverse([], Acc) ->
          │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ +naive_but_ok_reverse([], Acc) ->
          │ │ │      Acc.

          Each list element is copied only once. The growing result Acc is the right-hand │ │ │ -side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ -    vanilla_reverse(T, [H|Acc]);
          │ │ │ -vanilla_reverse([], Acc) ->
          │ │ │ +side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ +    vanilla_reverse(T, [H|Acc]);
          │ │ │ +vanilla_reverse([], Acc) ->
          │ │ │      Acc.

          In principle, this is slightly more efficient because the list element [H] │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ rewrites [H] ++ Acc to [H|Acc].

          │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ @@ -160,77 +160,77 @@ │ │ │ therefore harmless.

          │ │ │ │ │ │ │ │ │ │ │ │ Accidental Copying and Loss of Sharing │ │ │

          │ │ │

          When spawning a new process using a fun, one can accidentally copy more data to │ │ │ -the process than intended. For example:

          DO NOT

          accidental1(State) ->
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [State#state.info])
          │ │ │ -          end).

          The code in the fun will extract one element from the record and print it. The │ │ │ +the process than intended. For example:

          DO NOT

          accidental1(State) ->
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [State#state.info])
          │ │ │ +          end).

          The code in the fun will extract one element from the record and print it. The │ │ │ rest of the state record is not used. However, when the spawn/1 │ │ │ -function is executed, the entire record is copied to the newly created process.

          The same kind of problem can happen with a map:

          DO NOT

          accidental2(State) ->
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [map_get(info, State)])
          │ │ │ -          end).

          In the following example (part of a module implementing the gen_server │ │ │ -behavior) the created fun is sent to another process:

          DO NOT

          handle_call(give_me_a_fun, _From, State) ->
          │ │ │ -    Fun = fun() -> State#state.size =:= 42 end,
          │ │ │ -    {reply, Fun, State}.

          How bad that unnecessary copy is depends on the contents of the record or the │ │ │ -map.

          For example, if the state record is initialized like this:

          init1() ->
          │ │ │ -    #state{data=lists:seq(1, 10000)}.

          a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ +function is executed, the entire record is copied to the newly created process.

          The same kind of problem can happen with a map:

          DO NOT

          accidental2(State) ->
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [map_get(info, State)])
          │ │ │ +          end).

          In the following example (part of a module implementing the gen_server │ │ │ +behavior) the created fun is sent to another process:

          DO NOT

          handle_call(give_me_a_fun, _From, State) ->
          │ │ │ +    Fun = fun() -> State#state.size =:= 42 end,
          │ │ │ +    {reply, Fun, State}.

          How bad that unnecessary copy is depends on the contents of the record or the │ │ │ +map.

          For example, if the state record is initialized like this:

          init1() ->
          │ │ │ +    #state{data=lists:seq(1, 10000)}.

          a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ newly created process.

          An unnecessary copy of 10000 element list can be bad enough, but it can get even │ │ │ worse if the state record contains shared subterms. Here is a simple example │ │ │ -of a term with a shared subterm:

          {SubTerm, SubTerm}

          When a term is copied to another process, sharing of subterms will be lost and │ │ │ -the copied term can be many times larger than the original term. For example:

          init2() ->
          │ │ │ -    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
          │ │ │ -    #state{data=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ +of a term with a shared subterm:

          {SubTerm, SubTerm}

          When a term is copied to another process, sharing of subterms will be lost and │ │ │ +the copied term can be many times larger than the original term. For example:

          init2() ->
          │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
          │ │ │ +    #state{data=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ 131070 heap words. More details about │ │ │ loss off sharing are found in a later │ │ │ section.

          To avoid the problem, outside of the fun extract only the fields of the record │ │ │ -that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │ +that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │      Info = State#state.info,
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ -used:

          DO

          fixed_accidental2(State) ->
          │ │ │ -    Info = map_get(info, State),
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          │ │ │ + spawn(fun() -> │ │ │ + io:format("~p\n", [Info]) │ │ │ + end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ +used:

          DO

          fixed_accidental2(State) ->
          │ │ │ +    Info = map_get(info, State),
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [Info])
          │ │ │ +          end).

          │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1 │ │ │

          │ │ │

          Atoms are not garbage-collected. Once an atom is created, it is never removed. │ │ │ The emulator terminates if the limit for the number of atoms (1,048,576 by │ │ │ default) is reached.

          Therefore, converting arbitrary input strings to atoms can be dangerous in a │ │ │ system that runs continuously. If only certain well-defined atoms are allowed as │ │ │ input, list_to_existing_atom/1 or │ │ │ binary_to_existing_atom/1 can be used │ │ │ to guard against a denial-of-service attack. (All atoms that are allowed must │ │ │ have been created earlier, for example, by using all of them in a module │ │ │ and loading that module.)

          Using list_to_atom/1 to construct an atom that │ │ │ -is passed to apply/3 is quite expensive.

          DO NOT

          apply(list_to_atom("some_prefix"++Var), foo, Args)

          │ │ │ +is passed to apply/3 is quite expensive.

          DO NOT

          apply(list_to_atom("some_prefix"++Var), foo, Args)

          │ │ │ │ │ │ │ │ │ │ │ │ length/1 │ │ │

          │ │ │

          The time for calculating the length of a list is proportional to the length of │ │ │ the list, as opposed to tuple_size/1, │ │ │ byte_size/1, and bit_size/1, which all │ │ │ execute in constant time.

          Normally, there is no need to worry about the speed of length/1, │ │ │ because it is efficiently implemented in C. In time-critical code, you might │ │ │ want to avoid it if the input list could potentially be very long.

          Some uses of length/1 can be replaced by matching. For example, │ │ │ -the following code:

          foo(L) when length(L) >= 3 ->
          │ │ │ -    ...

          can be rewritten to:

          foo([_,_,_|_]=L) ->
          │ │ │ +the following code:

          foo(L) when length(L) >= 3 ->
          │ │ │ +    ...

          can be rewritten to:

          foo([_,_,_|_]=L) ->
          │ │ │     ...

          One slight difference is that length(L) fails if L is an │ │ │ improper list, while the pattern in the second code fragment accepts an improper │ │ │ list.

          │ │ │ │ │ │ │ │ │ │ │ │ setelement/3 │ │ │ @@ -238,18 +238,18 @@ │ │ │

          setelement/3 copies the tuple it modifies. Therefore, │ │ │ updating a tuple in a loop using setelement/3 creates a new │ │ │ copy of the tuple every time.

          There is one exception to the rule that the tuple is copied. If the compiler │ │ │ clearly can see that destructively updating the tuple would give the same result │ │ │ as if the tuple was copied, the call to setelement/3 is │ │ │ replaced with a special destructive setelement instruction. In the following │ │ │ code sequence, the first setelement/3 call copies the tuple │ │ │ -and modifies the ninth element:

          multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
          │ │ │ -    T1 = setelement(9, T0, bar),
          │ │ │ -    T2 = setelement(7, T1, foobar),
          │ │ │ -    setelement(5, T2, new_value).

          The two following setelement/3 calls modify the tuple in │ │ │ +and modifies the ninth element:

          multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
          │ │ │ +    T1 = setelement(9, T0, bar),
          │ │ │ +    T2 = setelement(7, T1, foobar),
          │ │ │ +    setelement(5, T2, new_value).

          The two following setelement/3 calls modify the tuple in │ │ │ place.

          For the optimization to be applied, all the following conditions must be true:

          • The tuple argument must be known to be a tuple of a known size.
          • The indices must be integer literals, not variables or expressions.
          • The indices must be given in descending order.
          • There must be no calls to another function in between the calls to │ │ │ setelement/3.
          • The tuple returned from one setelement/3 call must only be │ │ │ used in the subsequent call to setelement/3.

          If the code cannot be structured as in the multiple_setelement/1 example, the │ │ │ best way to modify multiple elements in a large tuple is to convert the tuple to │ │ │ a list, modify the list, and convert it back to a tuple.

          │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/conc_prog.html │ │ │ @@ -132,107 +132,107 @@ │ │ │ threads of execution in an Erlang program and to allow these threads to │ │ │ communicate with each other. In Erlang, each thread of execution is called a │ │ │ process.

          (Aside: the term "process" is usually used when the threads of execution share │ │ │ no data with each other and the term "thread" when they share data in some way. │ │ │ Threads of execution in Erlang share no data, that is why they are called │ │ │ processes).

          The Erlang BIF spawn is used to create a new process: │ │ │ spawn(Module, Exported_Function, List of Arguments). Consider the following │ │ │ -module:

          -module(tut14).
          │ │ │ +module:

          -module(tut14).
          │ │ │  
          │ │ │ --export([start/0, say_something/2]).
          │ │ │ +-export([start/0, say_something/2]).
          │ │ │  
          │ │ │ -say_something(What, 0) ->
          │ │ │ +say_something(What, 0) ->
          │ │ │      done;
          │ │ │ -say_something(What, Times) ->
          │ │ │ -    io:format("~p~n", [What]),
          │ │ │ -    say_something(What, Times - 1).
          │ │ │ -
          │ │ │ -start() ->
          │ │ │ -    spawn(tut14, say_something, [hello, 3]),
          │ │ │ -    spawn(tut14, say_something, [goodbye, 3]).
          5> c(tut14).
          │ │ │ -{ok,tut14}
          │ │ │ -6> tut14:say_something(hello, 3).
          │ │ │ +say_something(What, Times) ->
          │ │ │ +    io:format("~p~n", [What]),
          │ │ │ +    say_something(What, Times - 1).
          │ │ │ +
          │ │ │ +start() ->
          │ │ │ +    spawn(tut14, say_something, [hello, 3]),
          │ │ │ +    spawn(tut14, say_something, [goodbye, 3]).
          5> c(tut14).
          │ │ │ +{ok,tut14}
          │ │ │ +6> tut14:say_something(hello, 3).
          │ │ │  hello
          │ │ │  hello
          │ │ │  hello
          │ │ │  done

          As shown, the function say_something writes its first argument the number of │ │ │ times specified by second argument. The function start starts two Erlang │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ -the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │ +the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  <0.63.0>
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  hello
          │ │ │  goodbye

          Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ -last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ +last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ example shows how to use pids.

          Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

          ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ to output these as strings

          │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │

          │ │ │

          In the following example two processes are created and they send messages to │ │ │ -each other a number of times.

          -module(tut15).
          │ │ │ +each other a number of times.

          -module(tut15).
          │ │ │  
          │ │ │ --export([start/0, ping/2, pong/0]).
          │ │ │ +-export([start/0, ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_PID) ->
          │ │ │ +ping(0, Pong_PID) ->
          │ │ │      Pong_PID ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_PID) ->
          │ │ │ -    Pong_PID ! {ping, self()},
          │ │ │ +ping(N, Pong_PID) ->
          │ │ │ +    Pong_PID ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_PID).
          │ │ │ +    ping(N - 1, Pong_PID).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    Pong_PID = spawn(tut15, pong, []),
          │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ -{ok,tut15}
          │ │ │ -2> tut15: start().
          │ │ │ +start() ->
          │ │ │ +    Pong_PID = spawn(tut15, pong, []),
          │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ +{ok,tut15}
          │ │ │ +2> tut15: start().
          │ │ │  <0.36.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ -"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │ +Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ +"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │      finished ->
          │ │ │ -        io:format("Pong finished~n", []);
          │ │ │ -    {ping, Ping_PID} ->
          │ │ │ -        io:format("Pong received ping~n", []),
          │ │ │ +        io:format("Pong finished~n", []);
          │ │ │ +    {ping, Ping_PID} ->
          │ │ │ +        io:format("Pong received ping~n", []),
          │ │ │          Ping_PID ! pong,
          │ │ │ -        pong()
          │ │ │ +        pong()
          │ │ │  end.

          The receive construct is used to allow processes to wait for messages from │ │ │ other processes. It has the following format:

          receive
          │ │ │     pattern1 ->
          │ │ │         actions1;
          │ │ │     pattern2 ->
          │ │ │         actions2;
          │ │ │     ....
          │ │ │ @@ -253,84 +253,84 @@
          │ │ │  queue (keeping the first message and any other messages in the queue). If the
          │ │ │  second message does not match, the third message is tried, and so on, until the
          │ │ │  end of the queue is reached. If the end of the queue is reached, the process
          │ │ │  blocks (stops execution) and waits until a new message is received and this
          │ │ │  procedure is repeated.

          The Erlang implementation is "clever" and minimizes the number of times each │ │ │ message is tested against the patterns in each receive.

          Now back to the ping pong example.

          "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ -terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ +terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ process "ping":

          Ping_PID ! pong

          Notice how the operator "!" is used to send messages. The syntax of "!" is:

          Pid ! Message

          That is, Message (any Erlang term) is sent to the process with identity Pid.

          After sending the message pong to the process "ping", "pong" calls the pong │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ -another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ +another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ Ping_PID in the receive previously explained.)

          "Ping" now waits for a reply from "pong":

          receive
          │ │ │      pong ->
          │ │ │ -        io:format("Ping received pong~n", [])
          │ │ │ +        io:format("Ping received pong~n", [])
          │ │ │  end,

          It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ -the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ -occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │ +the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ +occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │      Pong_PID !  finished,
          │ │ │ -    io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ + io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ it has nothing left to do.

          │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │

          │ │ │

          In the above example, "pong" was first created to be able to give the identity │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ processes which need to know each other's identities are started independently │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ -using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ -the "pong" process:

          -module(tut16).
          │ │ │ +using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ +the "pong" process:

          -module(tut16).
          │ │ │  
          │ │ │ --export([start/0, ping/1, pong/0]).
          │ │ │ +-export([start/0, ping/1, pong/0]).
          │ │ │  
          │ │ │ -ping(0) ->
          │ │ │ +ping(0) ->
          │ │ │      pong ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N) ->
          │ │ │ -    pong ! {ping, self()},
          │ │ │ +ping(N) ->
          │ │ │ +    pong ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1).
          │ │ │ +    ping(N - 1).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    register(pong, spawn(tut16, pong, [])),
          │ │ │ -    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ -{ok, tut16}
          │ │ │ -3> tut16:start().
          │ │ │ +start() ->
          │ │ │ +    register(pong, spawn(tut16, pong, [])),
          │ │ │ +    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ +{ok, tut16}
          │ │ │ +3> tut16:start().
          │ │ │  <0.38.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ -process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ +Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ +process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │

          │ │ │

          Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ @@ -350,106 +350,106 @@ │ │ │ of the file. This is a requirement.

          When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ you must give it a name, for example:

          $ erl -sname my_name

          We will see more details of this later. If you want to experiment with │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ Erlang system running on a computer is called an Erlang node.

          (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ -in full.)

          Here is the ping pong example modified to run on two separate nodes:

          -module(tut17).
          │ │ │ +in full.)

          Here is the ping pong example modified to run on two separate nodes:

          -module(tut17).
          │ │ │  
          │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +ping(0, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! finished,
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ +ping(N, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start_pong() ->
          │ │ │ -    register(pong, spawn(tut17, pong, [])).
          │ │ │ +start_pong() ->
          │ │ │ +    register(pong, spawn(tut17, pong, [])).
          │ │ │  
          │ │ │ -start_ping(Pong_Node) ->
          │ │ │ -    spawn(tut17, ping, [3, Pong_Node]).

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ +start_ping(Pong_Node) -> │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

          On kosken (on a Linux/UNIX system):

          kosken> erl -sname ping
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │  (ping@kosken)1>

          On gollum:

          gollum> erl -sname pong
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │ -(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │ +(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │  true

          And the "ping" process on kosken is started (from the code above you can see │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ -system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │ +system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │  <0.37.0>
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  ping finished

          As shown, the ping pong program has run. On the "pong" side:

          (pong@gollum)2> 
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong finished
          │ │ │ -(pong@gollum)2> 

          Looking at the tut17 code, you see that the pong function itself is │ │ │ +(pong@gollum)2>

          Looking at the tut17 code, you see that the pong function itself is │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ -node the "ping" process is executes:

          {ping, Ping_PID} ->
          │ │ │ -    io:format("Pong received ping~n", []),
          │ │ │ +node the "ping" process is executes:

          {ping, Ping_PID} ->
          │ │ │ +    io:format("Pong received ping~n", []),
          │ │ │      Ping_PID ! pong,

          Thus, Erlang pids contain information about where the process executes. So if │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ -message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ +message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ registered_name.

          In the previous example, "ping" and "pong" were started from the shells of two │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ nodes.

          The next example is the ping pong program, yet again, but this time "ping" is │ │ │ -started in another node:

          -module(tut18).
          │ │ │ +started in another node:

          -module(tut18).
          │ │ │  
          │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +ping(0, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! finished,
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ +ping(N, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start(Ping_Node) ->
          │ │ │ -    register(pong, spawn(tut18, pong, [])),
          │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ -been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │ +start(Ping_Node) ->
          │ │ │ +    register(pong, spawn(tut18, pong, [])),
          │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ +been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │  <3934.39.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │ @@ -516,188 +516,188 @@
          │ │ │  %%% Started: messenger:client(Server_Node, Name)
          │ │ │  %%% To client: logoff
          │ │ │  %%% To client: {message_to, ToName, Message}
          │ │ │  %%%
          │ │ │  %%% Configuration: change the server_node() function to return the
          │ │ │  %%% name of the node where the messenger server runs
          │ │ │  
          │ │ │ --module(messenger).
          │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │ +-module(messenger).
          │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │  
          │ │ │  %%% Change the function below to return the name of the node where the
          │ │ │  %%% messenger server runs
          │ │ │ -server_node() ->
          │ │ │ +server_node() ->
          │ │ │      messenger@super.
          │ │ │  
          │ │ │  %%% This is the server process for the "messenger"
          │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
          │ │ │ -server(User_List) ->
          │ │ │ +server(User_List) ->
          │ │ │      receive
          │ │ │ -        {From, logon, Name} ->
          │ │ │ -            New_User_List = server_logon(From, Name, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, logoff} ->
          │ │ │ -            New_User_List = server_logoff(From, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, message_to, To, Message} ->
          │ │ │ -            server_transfer(From, To, Message, User_List),
          │ │ │ -            io:format("list is now: ~p~n", [User_List]),
          │ │ │ -            server(User_List)
          │ │ │ +        {From, logon, Name} ->
          │ │ │ +            New_User_List = server_logon(From, Name, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, logoff} ->
          │ │ │ +            New_User_List = server_logoff(From, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, message_to, To, Message} ->
          │ │ │ +            server_transfer(From, To, Message, User_List),
          │ │ │ +            io:format("list is now: ~p~n", [User_List]),
          │ │ │ +            server(User_List)
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Start the server
          │ │ │ -start_server() ->
          │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
          │ │ │ +start_server() ->
          │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server adds a new user to the user list
          │ │ │ -server_logon(From, Name, User_List) ->
          │ │ │ +server_logon(From, Name, User_List) ->
          │ │ │      %% check if logged on anywhere else
          │ │ │ -    case lists:keymember(Name, 2, User_List) of
          │ │ │ +    case lists:keymember(Name, 2, User_List) of
          │ │ │          true ->
          │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │              User_List;
          │ │ │          false ->
          │ │ │ -            From ! {messenger, logged_on},
          │ │ │ -            [{From, Name} | User_List]        %add user to the list
          │ │ │ +            From ! {messenger, logged_on},
          │ │ │ +            [{From, Name} | User_List]        %add user to the list
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Server deletes a user from the user list
          │ │ │ -server_logoff(From, User_List) ->
          │ │ │ -    lists:keydelete(From, 1, User_List).
          │ │ │ +server_logoff(From, User_List) ->
          │ │ │ +    lists:keydelete(From, 1, User_List).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server transfers a message between user
          │ │ │ -server_transfer(From, To, Message, User_List) ->
          │ │ │ +server_transfer(From, To, Message, User_List) ->
          │ │ │      %% check that the user is logged on and who he is
          │ │ │ -    case lists:keysearch(From, 1, User_List) of
          │ │ │ +    case lists:keysearch(From, 1, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ -        {value, {From, Name}} ->
          │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ +        {value, {From, Name}} ->
          │ │ │ +            server_transfer(From, Name, To, Message, User_List)
          │ │ │      end.
          │ │ │  %%% If the user exists, send the message
          │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
          │ │ │      %% Find the receiver and send the message
          │ │ │ -    case lists:keysearch(To, 2, User_List) of
          │ │ │ +    case lists:keysearch(To, 2, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ -        {value, {ToPid, To}} ->
          │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ -            From ! {messenger, sent}
          │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ +        {value, {ToPid, To}} ->
          │ │ │ +            ToPid ! {message_from, Name, Message},
          │ │ │ +            From ! {messenger, sent}
          │ │ │      end.
          │ │ │  
          │ │ │  
          │ │ │  %%% User Commands
          │ │ │ -logon(Name) ->
          │ │ │ -    case whereis(mess_client) of
          │ │ │ +logon(Name) ->
          │ │ │ +    case whereis(mess_client) of
          │ │ │          undefined ->
          │ │ │ -            register(mess_client,
          │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ +            register(mess_client,
          │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
          │ │ │          _ -> already_logged_on
          │ │ │      end.
          │ │ │  
          │ │ │ -logoff() ->
          │ │ │ +logoff() ->
          │ │ │      mess_client ! logoff.
          │ │ │  
          │ │ │ -message(ToName, Message) ->
          │ │ │ -    case whereis(mess_client) of % Test if the client is running
          │ │ │ +message(ToName, Message) ->
          │ │ │ +    case whereis(mess_client) of % Test if the client is running
          │ │ │          undefined ->
          │ │ │              not_logged_on;
          │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │               ok
          │ │ │  end.
          │ │ │  
          │ │ │  
          │ │ │  %%% The client process which runs on each server node
          │ │ │ -client(Server_Node, Name) ->
          │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ -    await_result(),
          │ │ │ -    client(Server_Node).
          │ │ │ +client(Server_Node, Name) ->
          │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ +    await_result(),
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │ -client(Server_Node) ->
          │ │ │ +client(Server_Node) ->
          │ │ │      receive
          │ │ │          logoff ->
          │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ -            exit(normal);
          │ │ │ -        {message_to, ToName, Message} ->
          │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ -            await_result();
          │ │ │ -        {message_from, FromName, Message} ->
          │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ +            exit(normal);
          │ │ │ +        {message_to, ToName, Message} ->
          │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ +            await_result();
          │ │ │ +        {message_from, FromName, Message} ->
          │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │      end,
          │ │ │ -    client(Server_Node).
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │  %%% wait for a response from the server
          │ │ │ -await_result() ->
          │ │ │ +await_result() ->
          │ │ │      receive
          │ │ │ -        {messenger, stop, Why} -> % Stop the client
          │ │ │ -            io:format("~p~n", [Why]),
          │ │ │ -            exit(normal);
          │ │ │ -        {messenger, What} ->  % Normal response
          │ │ │ -            io:format("~p~n", [What])
          │ │ │ +        {messenger, stop, Why} -> % Stop the client
          │ │ │ +            io:format("~p~n", [Why]),
          │ │ │ +            exit(normal);
          │ │ │ +        {messenger, What} ->  % Normal response
          │ │ │ +            io:format("~p~n", [What])
          │ │ │      end.

          To use this program, you need to:

          • Configure the server_node() function.
          • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ where you start Erlang.

          In the following example using this program, nodes are started on four different │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ can start several nodes on the same machine.

          Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ -c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ -true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │ +c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ +true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │  true
          │ │ │ -logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │ +logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │  true
          │ │ │ -logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │ +logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │  true
          │ │ │ -logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │ +logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │  ok
          │ │ │  sent

          Fred receives the message and sends a message to Peter and logs off:

          Message from peter: "hello"
          │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │  ok
          │ │ │  sent
          │ │ │ -(c3@gollum)3> messenger:logoff().
          │ │ │ -logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │ +(c3@gollum)3> messenger:logoff().
          │ │ │ +logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │  ok
          │ │ │  receiver_not_found

          But this fails as Fred has already logged off.

          First let us look at some of the new concepts that have been introduced.

          There are two versions of the server_transfer function: one with four │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ are regarded by Erlang as two separate functions.

          Notice how to write the server function so that it calls itself, through │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ function call. But this only works if there is no code after the call. │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ call. This would result in the process getting bigger and bigger for every loop.

          Functions in the lists module are used. This is a very useful module and a │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ -as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  true
          │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  false

          lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ -any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ +any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ {value,Tuple_Found} or the atom false.

          There are many very useful functions in the lists module.

          An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ in the system.

          A process terminates when there is nothing more for it to do, that is, the last │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ process running out of functions to call.

          The BIF whereis(RegisteredName) checks if a registered process │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ returned. If it does not exist, the atom undefined is returned.

          You should by now be able to understand most of the code in the │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ -user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ -sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ +user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ +sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ -the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ +the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ -the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ +the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ -sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │ +sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │

          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          rel(4) manual page in │ │ │ SASL), which specifies the ERTS version and lists all applications that are to │ │ │ be included in the new basic target system. An example is the following │ │ │ mysystem.rel file:

          %% mysystem.rel
          │ │ │ -{release,
          │ │ │ - {"MYSYSTEM", "FIRST"},
          │ │ │ - {erts, "5.10.4"},
          │ │ │ - [{kernel, "2.16.4"},
          │ │ │ -  {stdlib, "1.19.4"},
          │ │ │ -  {sasl, "2.3.4"},
          │ │ │ -  {pea, "1.0"}]}.

          The listed applications are not only original Erlang/OTP applications but │ │ │ +{release, │ │ │ + {"MYSYSTEM", "FIRST"}, │ │ │ + {erts, "5.10.4"}, │ │ │ + [{kernel, "2.16.4"}, │ │ │ + {stdlib, "1.19.4"}, │ │ │ + {sasl, "2.3.4"}, │ │ │ + {pea, "1.0"}]}.

          The listed applications are not only original Erlang/OTP applications but │ │ │ possibly also new applications that you have written (here exemplified by the │ │ │ application Pea (pea)).

          Step 2. Start Erlang/OTP from the directory where the mysystem.rel file │ │ │ resides:

          % erl -pa /home/user/target_system/myapps/pea-1.0/ebin

          The -pa argument prepends the path to the ebin directory for │ │ │ the Pea application to the code path.

          Step 3. Create the target system:

          1> target_system:create("mysystem").

          The function target_system:create/1 performs the following:

          1. Reads the file mysystem.rel and creates a new file plain.rel. │ │ │ The new file is identical to the original, except that it only │ │ │ lists the Kernel and STDLIB applications.

          2. From the files mysystem.rel and plain.rel creates the files │ │ │ mysystem.script, mysystem.boot, plain.script, and plain.boot │ │ │ @@ -242,25 +242,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating the Next Version │ │ │ │ │ │

            In this example the Pea application has been changed, and so are the │ │ │ applications ERTS, Kernel, STDLIB and SASL.

            Step 1. Create the file .rel:

            %% mysystem2.rel
            │ │ │ -{release,
            │ │ │ - {"MYSYSTEM", "SECOND"},
            │ │ │ - {erts, "6.0"},
            │ │ │ - [{kernel, "3.0"},
            │ │ │ -  {stdlib, "2.0"},
            │ │ │ -  {sasl, "2.4"},
            │ │ │ -  {pea, "2.0"}]}.

            Step 2. Create the application upgrade file (see │ │ │ +{release, │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ + {erts, "6.0"}, │ │ │ + [{kernel, "3.0"}, │ │ │ + {stdlib, "2.0"}, │ │ │ + {sasl, "2.4"}, │ │ │ + {pea, "2.0"}]}.

          Step 2. Create the application upgrade file (see │ │ │ appup in SASL) for Pea, for example:

          %% pea.appup
          │ │ │ -{"2.0",
          │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
          │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

          Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ +{"2.0", │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

      Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

      % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

      Step 4. Create the release upgrade file (see relup │ │ │ in SASL):

      1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
      │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
      │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

      Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ upgrade to.

      The path option is used for pointing out the old version of all applications. │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ @@ -292,21 +292,21 @@ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ [End]

      The above return value and output after the call to │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

      The node is accessible through a new pipe:

      % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

      List the available releases in the system:

      1> release_handler:which_releases().
      │ │ │ -[{"MYSYSTEM","SECOND",
      │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │ -  current},
      │ │ │ - {"MYSYSTEM","FIRST",
      │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ -  permanent}]

      Our new release, "SECOND", is now the current release, but we can also see that │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

      The node is accessible through a new pipe:

      % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

      List the available releases in the system:

      1> release_handler:which_releases().
      │ │ │ +[{"MYSYSTEM","SECOND",
      │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │ +  current},
      │ │ │ + {"MYSYSTEM","FIRST",
      │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ +  permanent}]

      Our new release, "SECOND", is now the current release, but we can also see that │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ restarted now, it would come up running the "FIRST" release again.

      Step 3. Make the new release permanent:

      2> release_handler:make_permanent("SECOND").

      Check the releases again:

      3> release_handler:which_releases().
      │ │ │  [{"MYSYSTEM","SECOND",
      │ │ │    ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │    permanent},
      │ │ │   {"MYSYSTEM","FIRST",
      │ │ │    ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ @@ -315,268 +315,268 @@
      │ │ │    
      │ │ │      
      │ │ │    
      │ │ │    Listing of target_system.erl
      │ │ │  
      │ │ │  

      This module can also be found in the examples directory of the SASL │ │ │ application.

      
      │ │ │ --module(target_system).
      │ │ │ --export([create/1, create/2, install/2]).
      │ │ │ +-module(target_system).
      │ │ │ +-export([create/1, create/2, install/2]).
      │ │ │  
      │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
      │ │ │  %% .script etc.
      │ │ │  %%
      │ │ │  
      │ │ │  %% create(RelFileName)
      │ │ │  %%
      │ │ │ -create(RelFileName) ->
      │ │ │ -    create(RelFileName,[]).
      │ │ │ +create(RelFileName) ->
      │ │ │ +    create(RelFileName,[]).
      │ │ │  
      │ │ │ -create(RelFileName,SystoolsOpts) ->
      │ │ │ +create(RelFileName,SystoolsOpts) ->
      │ │ │      RelFile = RelFileName ++ ".rel",
      │ │ │ -    Dir = filename:dirname(RelFileName),
      │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
      │ │ │ +    Dir = filename:dirname(RelFileName),
      │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
      │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
      │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
      │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
      │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
      │ │ │ -              [PlainRelFile, RelFile]),
      │ │ │ -    {release,
      │ │ │ -     {RelName, RelVsn},
      │ │ │ -     {erts, ErtsVsn},
      │ │ │ -     AppVsns} = RelSpec,
      │ │ │ -    PlainRelSpec = {release,
      │ │ │ -                    {RelName, RelVsn},
      │ │ │ -                    {erts, ErtsVsn},
      │ │ │ -                    lists:filter(fun({kernel, _}) ->
      │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
      │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
      │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
      │ │ │ +              [PlainRelFile, RelFile]),
      │ │ │ +    {release,
      │ │ │ +     {RelName, RelVsn},
      │ │ │ +     {erts, ErtsVsn},
      │ │ │ +     AppVsns} = RelSpec,
      │ │ │ +    PlainRelSpec = {release,
      │ │ │ +                    {RelName, RelVsn},
      │ │ │ +                    {erts, ErtsVsn},
      │ │ │ +                    lists:filter(fun({kernel, _}) ->
      │ │ │                                           true;
      │ │ │ -                                    ({stdlib, _}) ->
      │ │ │ +                                    ({stdlib, _}) ->
      │ │ │                                           true;
      │ │ │ -                                    (_) ->
      │ │ │ +                                    (_) ->
      │ │ │                                           false
      │ │ │ -                                 end, AppVsns)
      │ │ │ -                   },
      │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
      │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
      │ │ │ -    file:close(Fd),
      │ │ │ -
      │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
      │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
      │ │ │ -
      │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ -              [RelFileName, RelFileName]),
      │ │ │ -    make_script(RelFileName,SystoolsOpts),
      │ │ │ +                                 end, AppVsns)
      │ │ │ +                   },
      │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
      │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
      │ │ │ +    file:close(Fd),
      │ │ │ +
      │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
      │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
      │ │ │ +
      │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ +              [RelFileName, RelFileName]),
      │ │ │ +    make_script(RelFileName,SystoolsOpts),
      │ │ │  
      │ │ │      TarFileName = RelFileName ++ ".tar.gz",
      │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
      │ │ │ -    make_tar(RelFileName,SystoolsOpts),
      │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
      │ │ │ +    make_tar(RelFileName,SystoolsOpts),
      │ │ │  
      │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
      │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
      │ │ │ -    file:make_dir(TmpDir),
      │ │ │ -
      │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
      │ │ │ -    extract_tar(TarFileName, TmpDir),
      │ │ │ -
      │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
      │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
      │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
      │ │ │ -              [ErtsBinDir]),
      │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
      │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
      │ │ │ -
      │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
      │ │ │ -    file:make_dir(TmpBinDir),
      │ │ │ -
      │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
      │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
      │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
      │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
      │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
      │ │ │ +    file:make_dir(TmpDir),
      │ │ │ +
      │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
      │ │ │ +    extract_tar(TarFileName, TmpDir),
      │ │ │ +
      │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
      │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
      │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
      │ │ │ +              [ErtsBinDir]),
      │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
      │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
      │ │ │ +
      │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
      │ │ │ +    file:make_dir(TmpBinDir),
      │ │ │ +
      │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
      │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
      │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
      │ │ │  
      │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
      │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
      │ │ │                "~ts to ~ts ...~n",
      │ │ │ -              [ErtsBinDir, TmpBinDir]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
      │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
      │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
      │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
      │ │ │ +              [ErtsBinDir, TmpBinDir]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
      │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
      │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
      │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
      │ │ │  
      │ │ │      %% This is needed if 'start' script created from 'start.src' shall
      │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
      │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
      │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
      │ │ │ -    ok = file:make_dir(TmpLogDir),
      │ │ │ -
      │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
      │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
      │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
      │ │ │ -    write_file(StartErlDataFile, StartErlData),
      │ │ │ -
      │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
      │ │ │ -	      [TarFileName,TmpDir]),
      │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
      │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
      │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
      │ │ │ +    ok = file:make_dir(TmpLogDir),
      │ │ │ +
      │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
      │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
      │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
      │ │ │ +    write_file(StartErlDataFile, StartErlData),
      │ │ │ +
      │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
      │ │ │ +	      [TarFileName,TmpDir]),
      │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
      │ │ │      %% {ok, Cwd} = file:get_cwd(),
      │ │ │      %% file:set_cwd("tmp"),
      │ │ │      ErtsDir = "erts-"++ErtsVsn,
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
      │ │ │ -    erl_tar:close(Tar),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
      │ │ │ +    erl_tar:close(Tar),
      │ │ │      %% file:set_cwd(Cwd),
      │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
      │ │ │ -    remove_dir_tree(TmpDir),
      │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
      │ │ │ +    remove_dir_tree(TmpDir),
      │ │ │      ok.
      │ │ │  
      │ │ │  
      │ │ │ -install(RelFileName, RootDir) ->
      │ │ │ +install(RelFileName, RootDir) ->
      │ │ │      TarFile = RelFileName ++ ".tar.gz",
      │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
      │ │ │ -    extract_tar(TarFile, RootDir),
      │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
      │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
      │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
      │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
      │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
      │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
      │ │ │ -              "form erl, start and start_erl ...\n"),
      │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
      │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
      │ │ │ -                      [preserve]),
      │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
      │ │ │ +    extract_tar(TarFile, RootDir),
      │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
      │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
      │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
      │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
      │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
      │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
      │ │ │ +              "form erl, start and start_erl ...\n"),
      │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
      │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
      │ │ │ +                      [preserve]),
      │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
      │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
      │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
      │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
      │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
      │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
      │ │ │  
      │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
      │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
      │ │ │ -					    filename:basename(RelFileName)])).
      │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
      │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
      │ │ │ +					    filename:basename(RelFileName)])).
      │ │ │  
      │ │ │  %% LOCALS
      │ │ │  
      │ │ │  %% make_script(RelFileName,Opts)
      │ │ │  %%
      │ │ │ -make_script(RelFileName,Opts) ->
      │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
      │ │ │ -				       {outdir,filename:dirname(RelFileName)}
      │ │ │ -				       |Opts]).
      │ │ │ +make_script(RelFileName,Opts) ->
      │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
      │ │ │ +				       {outdir,filename:dirname(RelFileName)}
      │ │ │ +				       |Opts]).
      │ │ │  
      │ │ │  %% make_tar(RelFileName,Opts)
      │ │ │  %%
      │ │ │ -make_tar(RelFileName,Opts) ->
      │ │ │ -    RootDir = code:root_dir(),
      │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
      │ │ │ -				    {outdir,filename:dirname(RelFileName)}
      │ │ │ -				    |Opts]).
      │ │ │ +make_tar(RelFileName,Opts) ->
      │ │ │ +    RootDir = code:root_dir(),
      │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
      │ │ │ +				    {outdir,filename:dirname(RelFileName)}
      │ │ │ +				    |Opts]).
      │ │ │  
      │ │ │  %% extract_tar(TarFile, DestDir)
      │ │ │  %%
      │ │ │ -extract_tar(TarFile, DestDir) ->
      │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
      │ │ │ +extract_tar(TarFile, DestDir) ->
      │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
      │ │ │  
      │ │ │ -create_RELEASES(DestDir, RelFileName) ->
      │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
      │ │ │ +create_RELEASES(DestDir, RelFileName) ->
      │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
      │ │ │  
      │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ -    lists:foreach(fun(Script) ->
      │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
      │ │ │ -                                           Vars, Opts)
      │ │ │ -                  end, Scripts).
      │ │ │ -
      │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
      │ │ │ -               filename:join([DestDir, Script]),
      │ │ │ -               Vars, Opts).
      │ │ │ -
      │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
      │ │ │ -    {ok, Conts} = read_txt_file(Src),
      │ │ │ -    NConts = subst(Conts, Vars),
      │ │ │ -    write_file(Dest, NConts),
      │ │ │ -    case lists:member(preserve, Opts) of
      │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ +    lists:foreach(fun(Script) ->
      │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
      │ │ │ +                                           Vars, Opts)
      │ │ │ +                  end, Scripts).
      │ │ │ +
      │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
      │ │ │ +               filename:join([DestDir, Script]),
      │ │ │ +               Vars, Opts).
      │ │ │ +
      │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
      │ │ │ +    {ok, Conts} = read_txt_file(Src),
      │ │ │ +    NConts = subst(Conts, Vars),
      │ │ │ +    write_file(Dest, NConts),
      │ │ │ +    case lists:member(preserve, Opts) of
      │ │ │          true ->
      │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ -            file:write_file_info(Dest, FileInfo);
      │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ +            file:write_file_info(Dest, FileInfo);
      │ │ │          false ->
      │ │ │              ok
      │ │ │      end.
      │ │ │  
      │ │ │  %% subst(Str, Vars)
      │ │ │  %% Vars = [{Var, Val}]
      │ │ │  %% Var = Val = string()
      │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
      │ │ │  %% of variables in Vars.
      │ │ │  %%
      │ │ │ -subst(Str, Vars) ->
      │ │ │ -    subst(Str, Vars, []).
      │ │ │ +subst(Str, Vars) ->
      │ │ │ +    subst(Str, Vars, []).
      │ │ │  
      │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([C| Rest], Vars, Result) ->
      │ │ │ -    subst(Rest, Vars, [C| Result]);
      │ │ │ -subst([], _Vars, Result) ->
      │ │ │ -    lists:reverse(Result).
      │ │ │ -
      │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
      │ │ │ -    Key = lists:reverse(VarAcc),
      │ │ │ -    case lists:keysearch(Key, 1, Vars) of
      │ │ │ -        {value, {Key, Value}} ->
      │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
      │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([C| Rest], Vars, Result) ->
      │ │ │ +    subst(Rest, Vars, [C| Result]);
      │ │ │ +subst([], _Vars, Result) ->
      │ │ │ +    lists:reverse(Result).
      │ │ │ +
      │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
      │ │ │ +    Key = lists:reverse(VarAcc),
      │ │ │ +    case lists:keysearch(Key, 1, Vars) of
      │ │ │ +        {value, {Key, Value}} ->
      │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
      │ │ │          false ->
      │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
      │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
      │ │ │      end;
      │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
      │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
      │ │ │ -subst_var([], Vars, Result, VarAcc) ->
      │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
      │ │ │ -
      │ │ │ -copy_file(Src, Dest) ->
      │ │ │ -    copy_file(Src, Dest, []).
      │ │ │ -
      │ │ │ -copy_file(Src, Dest, Opts) ->
      │ │ │ -    {ok,_} = file:copy(Src, Dest),
      │ │ │ -    case lists:member(preserve, Opts) of
      │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
      │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
      │ │ │ +subst_var([], Vars, Result, VarAcc) ->
      │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
      │ │ │ +
      │ │ │ +copy_file(Src, Dest) ->
      │ │ │ +    copy_file(Src, Dest, []).
      │ │ │ +
      │ │ │ +copy_file(Src, Dest, Opts) ->
      │ │ │ +    {ok,_} = file:copy(Src, Dest),
      │ │ │ +    case lists:member(preserve, Opts) of
      │ │ │          true ->
      │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ -            file:write_file_info(Dest, FileInfo);
      │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ +            file:write_file_info(Dest, FileInfo);
      │ │ │          false ->
      │ │ │              ok
      │ │ │      end.
      │ │ │  
      │ │ │ -write_file(FName, Conts) ->
      │ │ │ -    Enc = file:native_name_encoding(),
      │ │ │ -    {ok, Fd} = file:open(FName, [write]),
      │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
      │ │ │ -    file:close(Fd).
      │ │ │ -
      │ │ │ -read_txt_file(File) ->
      │ │ │ -    {ok, Bin} = file:read_file(File),
      │ │ │ -    {ok, binary_to_list(Bin)}.
      │ │ │ -
      │ │ │ -remove_dir_tree(Dir) ->
      │ │ │ -    remove_all_files(".", [Dir]).
      │ │ │ -
      │ │ │ -remove_all_files(Dir, Files) ->
      │ │ │ -    lists:foreach(fun(File) ->
      │ │ │ -                          FilePath = filename:join([Dir, File]),
      │ │ │ -                          case filelib:is_dir(FilePath) of
      │ │ │ +write_file(FName, Conts) ->
      │ │ │ +    Enc = file:native_name_encoding(),
      │ │ │ +    {ok, Fd} = file:open(FName, [write]),
      │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
      │ │ │ +    file:close(Fd).
      │ │ │ +
      │ │ │ +read_txt_file(File) ->
      │ │ │ +    {ok, Bin} = file:read_file(File),
      │ │ │ +    {ok, binary_to_list(Bin)}.
      │ │ │ +
      │ │ │ +remove_dir_tree(Dir) ->
      │ │ │ +    remove_all_files(".", [Dir]).
      │ │ │ +
      │ │ │ +remove_all_files(Dir, Files) ->
      │ │ │ +    lists:foreach(fun(File) ->
      │ │ │ +                          FilePath = filename:join([Dir, File]),
      │ │ │ +                          case filelib:is_dir(FilePath) of
      │ │ │                                true ->
      │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
      │ │ │ -                                  remove_all_files(FilePath, DirFiles),
      │ │ │ -                                  file:del_dir(FilePath);
      │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
      │ │ │ +                                  remove_all_files(FilePath, DirFiles),
      │ │ │ +                                  file:del_dir(FilePath);
      │ │ │                                _ ->
      │ │ │ -                                  file:delete(FilePath)
      │ │ │ +                                  file:delete(FilePath)
      │ │ │                            end
      │ │ │ -                  end, Files).
      │ │ │ + end, Files).
      │ │ │ │ │ │ │ │ │
      │ │ │
      │ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │

      When working with floats you may not see what you expect when printing or doing │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

      1> 0.1+0.2.
      │ │ │ -0.30000000000000004

      The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

      1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
      │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
      │ │ │ -{3.602879701896397e16, true,
      │ │ │ - 3.602879701896397e16, false}.

      The value 36028797018963968 can be represented exactly as a float value but │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ +{3.602879701896397e16, true,
    │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ (=36028797018963970.0) as all values in the range │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ @@ -237,52 +237,52 @@ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │  <<10,20>>
    │ │ │  2> <<"ABC">>.
    │ │ │  <<"ABC">>
    │ │ │  3> <<1:1,0:1>>.
    │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ term is a bit string, and the is_binary/1 │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │  true
    │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ +2> is_binary(<<1:1>>).
    │ │ │  false
    │ │ │ -3> is_binary(<<42>>).
    │ │ │ +3> is_binary(<<42>>).
    │ │ │  true
    │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │

    │ │ │

    A term that is unique │ │ │ among connected nodes. A reference is created by calling the │ │ │ make_ref/0 BIF. The │ │ │ is_reference/1 BIF tests whether a term │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ -2> is_reference(Ref).
    │ │ │ +2> is_reference(Ref).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │

    │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3

    The is_function/1 and is_function/2 │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ -2> is_function(F).
    │ │ │ +2> is_function(F).
    │ │ │  true
    │ │ │ -3> is_function(F, 0).
    │ │ │ +3> is_function(F, 0).
    │ │ │  true
    │ │ │ -4> is_function(F, 1).
    │ │ │ +4> is_function(F, 1).
    │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │

    │ │ │ @@ -300,94 +300,94 @@ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ creating a new process, the parent │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ a message, which is the case when calling the │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ when sending a process a signal. The │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ --export([loop/0]).
    │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ +-export([loop/0]).
    │ │ │  
    │ │ │ -loop() ->
    │ │ │ +loop() ->
    │ │ │      receive
    │ │ │          who_are_you ->
    │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ -            loop()
    │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ +            loop()
    │ │ │      end.
    │ │ │  
    │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ +1> P = spawn(m, loop, []).
    │ │ │  <0.58.0>
    │ │ │  2> P ! who_are_you.
    │ │ │  I am <0.58.0>
    │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │

    │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ -{adam,24,{july,29}}
    │ │ │ -2> element(1,P).
    │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ +{adam,24,{july,29}}
    │ │ │ +2> element(1,P).
    │ │ │  adam
    │ │ │ -3> element(3,P).
    │ │ │ -{july,29}
    │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ -{adam,25,{july,29}}
    │ │ │ -5> tuple_size(P).
    │ │ │ +3> element(3,P).
    │ │ │ +{july,29}
    │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ +{adam,25,{july,29}}
    │ │ │ +5> tuple_size(P).
    │ │ │  3
    │ │ │ -6> tuple_size({}).
    │ │ │ +6> tuple_size({}).
    │ │ │  0
    │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ +7> is_tuple({a,b,c}).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │

    │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ and value parts of the pair are called elements. The number of association │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ -2> maps:get(name, M1).
    │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ +2> maps:get(name, M1).
    │ │ │  adam
    │ │ │ -3> maps:get(date, M1).
    │ │ │ -{july,29}
    │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ -5> map_size(M).
    │ │ │ +3> maps:get(date, M1).
    │ │ │ +{july,29}
    │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ +5> map_size(M).
    │ │ │  3
    │ │ │ -6> map_size(#{}).
    │ │ │ +6> map_size(#{}).
    │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │

    │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ -[a,2,{c,4}]
    │ │ │ -2> [H|T] = L1.
    │ │ │ -[a,2,{c,4}]
    │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ +[a,2,{c,4}]
    │ │ │ +2> [H|T] = L1.
    │ │ │ +[a,2,{c,4}]
    │ │ │  3> H.
    │ │ │  a
    │ │ │  4> T.
    │ │ │ -[2,{c,4}]
    │ │ │ -5> L2 = [d|T].
    │ │ │ -[d,2,{c,4}]
    │ │ │ -6> length(L1).
    │ │ │ +[2,{c,4}]
    │ │ │ +5> L2 = [d|T].
    │ │ │ +[d,2,{c,4}]
    │ │ │ +6> length(L1).
    │ │ │  3
    │ │ │ -7> length([]).
    │ │ │ +7> length([]).
    │ │ │  0

    A collection of list processing functions are found in module │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │

    │ │ │ @@ -507,41 +507,41 @@ │ │ │ Record │ │ │ │ │ │

    A record is a data structure for storing a fixed number of elements. It has │ │ │ named fields and is similar to a struct in C. However, a record is not a true │ │ │ data type. Instead, record expressions are translated to tuple expressions │ │ │ during compilation. Therefore, record expressions are not understood by the │ │ │ shell unless special actions are taken. For details, see module shell │ │ │ -in STDLIB.

    Examples:

    -module(person).
    │ │ │ --export([new/2]).
    │ │ │ +in STDLIB.

    Examples:

    -module(person).
    │ │ │ +-export([new/2]).
    │ │ │  
    │ │ │ --record(person, {name, age}).
    │ │ │ +-record(person, {name, age}).
    │ │ │  
    │ │ │ -new(Name, Age) ->
    │ │ │ -    #person{name=Name, age=Age}.
    │ │ │ +new(Name, Age) ->
    │ │ │ +    #person{name=Name, age=Age}.
    │ │ │  
    │ │ │ -1> person:new(ernie, 44).
    │ │ │ -{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ +1> person:new(ernie, 44). │ │ │ +{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ found in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Boolean │ │ │

    │ │ │

    There is no Boolean data type in Erlang. Instead the atoms true and false │ │ │ are used to denote Boolean values. The is_boolean/1 │ │ │ BIF tests whether a term is a boolean.

    Examples:

    1> 2 =< 3.
    │ │ │  true
    │ │ │  2> true or false.
    │ │ │  true
    │ │ │ -3> is_boolean(true).
    │ │ │ +3> is_boolean(true).
    │ │ │  true
    │ │ │ -4> is_boolean(false).
    │ │ │ +4> is_boolean(false).
    │ │ │  true
    │ │ │ -5> is_boolean(ok).
    │ │ │ +5> is_boolean(ok).
    │ │ │  false

    │ │ │ │ │ │ │ │ │ │ │ │ Escape Sequences │ │ │

    │ │ │

    Within strings ("-delimited), quoted atoms, and the content of │ │ │ @@ -559,47 +559,47 @@ │ │ │ ~b or ~s sigils the escape sequences for normal │ │ │ strings, above, are used.

    Change

    Triple-quoted strings and sigils were introduced in Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ Type Conversions │ │ │

    │ │ │ -

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ +

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │  "hello"
    │ │ │ -2> list_to_atom("hello").
    │ │ │ +2> list_to_atom("hello").
    │ │ │  hello
    │ │ │ -3> binary_to_list(<<"hello">>).
    │ │ │ +3> binary_to_list(<<"hello">>).
    │ │ │  "hello"
    │ │ │ -4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ +4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │  "hello"
    │ │ │ -5> list_to_binary("hello").
    │ │ │ -<<104,101,108,108,111>>
    │ │ │ -6> float_to_list(7.0).
    │ │ │ +5> list_to_binary("hello").
    │ │ │ +<<104,101,108,108,111>>
    │ │ │ +6> float_to_list(7.0).
    │ │ │  "7.00000000000000000000e+00"
    │ │ │ -7> list_to_float("7.000e+00").
    │ │ │ +7> list_to_float("7.000e+00").
    │ │ │  7.0
    │ │ │ -8> integer_to_list(77).
    │ │ │ +8> integer_to_list(77).
    │ │ │  "77"
    │ │ │ -9> list_to_integer("77").
    │ │ │ +9> list_to_integer("77").
    │ │ │  77
    │ │ │ -10> tuple_to_list({a,b,c}).
    │ │ │ -[a,b,c]
    │ │ │ -11> list_to_tuple([a,b,c]).
    │ │ │ -{a,b,c}
    │ │ │ -12> term_to_binary({a,b,c}).
    │ │ │ -<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ -13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ -{a,b,c}
    │ │ │ -14> binary_to_integer(<<"77">>).
    │ │ │ +10> tuple_to_list({a,b,c}).
    │ │ │ +[a,b,c]
    │ │ │ +11> list_to_tuple([a,b,c]).
    │ │ │ +{a,b,c}
    │ │ │ +12> term_to_binary({a,b,c}).
    │ │ │ +<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ +13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ +{a,b,c}
    │ │ │ +14> binary_to_integer(<<"77">>).
    │ │ │  77
    │ │ │ -15> integer_to_binary(77).
    │ │ │ -<<"77">>
    │ │ │ -16> float_to_binary(7.0).
    │ │ │ -<<"7.00000000000000000000e+00">>
    │ │ │ -17> binary_to_float(<<"7.000e+00">>).
    │ │ │ +15> integer_to_binary(77).
    │ │ │ +<<"77">>
    │ │ │ +16> float_to_binary(7.0).
    │ │ │ +<<"7.00000000000000000000e+00">>
    │ │ │ +17> binary_to_float(<<"7.000e+00">>).
    │ │ │  7.0
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    -module(ch1).
    │ │ │ --export([start/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/0]).
    │ │ │ +respectively.

    -module(ch1).
    │ │ │ +-export([start/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/0]).
    │ │ │  
    │ │ │ -start() ->
    │ │ │ -    spawn(ch1, init, []).
    │ │ │ +start() ->
    │ │ │ +    spawn(ch1, init, []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    ch1 ! {self(), alloc},
    │ │ │ +alloc() ->
    │ │ │ +    ch1 ! {self(), alloc},
    │ │ │      receive
    │ │ │ -        {ch1, Res} ->
    │ │ │ +        {ch1, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    ch1 ! {free, Ch},
    │ │ │ +free(Ch) ->
    │ │ │ +    ch1 ! {free, Ch},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init() ->
    │ │ │ -    register(ch1, self()),
    │ │ │ -    Chs = channels(),
    │ │ │ -    loop(Chs).
    │ │ │ +init() ->
    │ │ │ +    register(ch1, self()),
    │ │ │ +    Chs = channels(),
    │ │ │ +    loop(Chs).
    │ │ │  
    │ │ │ -loop(Chs) ->
    │ │ │ +loop(Chs) ->
    │ │ │      receive
    │ │ │ -        {From, alloc} ->
    │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ -            From ! {ch1, Ch},
    │ │ │ -            loop(Chs2);
    │ │ │ -        {free, Ch} ->
    │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ -            loop(Chs2)
    │ │ │ -    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ --export([start/1]).
    │ │ │ --export([call/2, cast/2]).
    │ │ │ --export([init/1]).
    │ │ │ +        {From, alloc} ->
    │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ +            From ! {ch1, Ch},
    │ │ │ +            loop(Chs2);
    │ │ │ +        {free, Ch} ->
    │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ +            loop(Chs2)
    │ │ │ +    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ +-export([start/1]).
    │ │ │ +-export([call/2, cast/2]).
    │ │ │ +-export([init/1]).
    │ │ │  
    │ │ │ -start(Mod) ->
    │ │ │ -    spawn(server, init, [Mod]).
    │ │ │ +start(Mod) ->
    │ │ │ +    spawn(server, init, [Mod]).
    │ │ │  
    │ │ │ -call(Name, Req) ->
    │ │ │ -    Name ! {call, self(), Req},
    │ │ │ +call(Name, Req) ->
    │ │ │ +    Name ! {call, self(), Req},
    │ │ │      receive
    │ │ │ -        {Name, Res} ->
    │ │ │ +        {Name, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -cast(Name, Req) ->
    │ │ │ -    Name ! {cast, Req},
    │ │ │ +cast(Name, Req) ->
    │ │ │ +    Name ! {cast, Req},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init(Mod) ->
    │ │ │ -    register(Mod, self()),
    │ │ │ -    State = Mod:init(),
    │ │ │ -    loop(Mod, State).
    │ │ │ +init(Mod) ->
    │ │ │ +    register(Mod, self()),
    │ │ │ +    State = Mod:init(),
    │ │ │ +    loop(Mod, State).
    │ │ │  
    │ │ │ -loop(Mod, State) ->
    │ │ │ +loop(Mod, State) ->
    │ │ │      receive
    │ │ │ -        {call, From, Req} ->
    │ │ │ -            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ -            From ! {Mod, Res},
    │ │ │ -            loop(Mod, State2);
    │ │ │ -        {cast, Req} ->
    │ │ │ -            State2 = Mod:handle_cast(Req, State),
    │ │ │ -            loop(Mod, State2)
    │ │ │ -    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ --export([start/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ -
    │ │ │ -start() ->
    │ │ │ -    server:start(ch2).
    │ │ │ -
    │ │ │ -alloc() ->
    │ │ │ -    server:call(ch2, alloc).
    │ │ │ -
    │ │ │ -free(Ch) ->
    │ │ │ -    server:cast(ch2, {free, Ch}).
    │ │ │ +        {call, From, Req} ->
    │ │ │ +            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ +            From ! {Mod, Res},
    │ │ │ +            loop(Mod, State2);
    │ │ │ +        {cast, Req} ->
    │ │ │ +            State2 = Mod:handle_cast(Req, State),
    │ │ │ +            loop(Mod, State2)
    │ │ │ +    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ +-export([start/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ +
    │ │ │ +start() ->
    │ │ │ +    server:start(ch2).
    │ │ │ +
    │ │ │ +alloc() ->
    │ │ │ +    server:call(ch2, alloc).
    │ │ │ +
    │ │ │ +free(Ch) ->
    │ │ │ +    server:cast(ch2, {free, Ch}).
    │ │ │  
    │ │ │ -init() ->
    │ │ │ -    channels().
    │ │ │ +init() ->
    │ │ │ +    channels().
    │ │ │  
    │ │ │ -handle_call(alloc, Chs) ->
    │ │ │ -    alloc(Chs). % => {Ch,Chs2}
    │ │ │ +handle_call(alloc, Chs) ->
    │ │ │ +    alloc(Chs). % => {Ch,Chs2}
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ the client functions. This means that the name can be changed without │ │ │ affecting them.
    • The protocol (messages sent to and received from the server) is also hidden. │ │ │ This is good programming practice and allows one to change the protocol │ │ │ without changing the code using the interface functions.
    • The functionality of server can be extended without having to change ch2 │ │ │ or any other callback module.

    In ch1.erl and ch2.erl above, the implementation of channels/0, alloc/1, │ │ │ and free/2 has been intentionally left out, as it is not relevant to the │ │ │ example. For completeness, one way to write these functions is given below. This │ │ │ is an example only, a realistic implementation must be able to handle situations │ │ │ -like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ -   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ +like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ +   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │  
    │ │ │ -alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ -   {H, {[H|Allocated], T}}.
    │ │ │ +alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ +   {H, {[H|Allocated], T}}.
    │ │ │  
    │ │ │ -free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ -   case lists:member(Ch, Alloc) of
    │ │ │ +free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ +   case lists:member(Ch, Alloc) of
    │ │ │        true ->
    │ │ │ -         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ +         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │        false ->
    │ │ │           Channels
    │ │ │     end.

    Code written without using behaviours can be more efficient, but the increased │ │ │ efficiency is at the expense of generality. The ability to manage all │ │ │ applications in the system in a consistent manner is important.

    Using behaviours also makes it easier to read and understand code written by │ │ │ other programmers. Improvised programming structures, while possibly more │ │ │ efficient, are always more difficult to understand.

    The server module corresponds, greatly simplified, to the Erlang/OTP behaviour │ │ │ gen_server.

    The standard Erlang/OTP behaviours are:

    • gen_server

      For implementing the server of a client-server relation

    • gen_statem

      For implementing state machines

    • gen_event

      For implementing event handling functionality

    • supervisor

      For implementing a supervisor in a supervision tree

    The compiler understands the module attribute -behaviour(Behaviour) and issues │ │ │ -warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  ...
    │ │ │  
    │ │ │ -3> c(chs3).
    │ │ │ +3> c(chs3).
    │ │ │  ./chs3.erl:10: Warning: undefined call-back function handle_call/3
    │ │ │ -{ok,chs3}

    │ │ │ +{ok,chs3}

    │ │ │ │ │ │ │ │ │ │ │ │ Applications │ │ │

    │ │ │

    Erlang/OTP comes with a number of components, each implementing some specific │ │ │ functionality. Components are with Erlang/OTP terminology called applications. │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/distributed.html │ │ │ @@ -142,25 +142,25 @@ │ │ │ │ │ │

    A node is an executing Erlang runtime system that has been given a name, using │ │ │ the command-line flag -name (long names) or │ │ │ -sname (short names).

    The format of the node name is an atom name@host. name is the name given by │ │ │ the user. host is the full host name if long names are used, or the first part │ │ │ of the host name if short names are used. Function node() │ │ │ returns the name of the node.

    Example:

    % erl -name dilbert
    │ │ │ -(dilbert@uab.ericsson.se)1> node().
    │ │ │ +(dilbert@uab.ericsson.se)1> node().
    │ │ │  'dilbert@uab.ericsson.se'
    │ │ │  
    │ │ │  % erl -sname dilbert
    │ │ │ -(dilbert@uab)1> node().
    │ │ │ +(dilbert@uab)1> node().
    │ │ │  dilbert@uab

    The node name can also be given in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ -1> node().
    │ │ │ +1> node().
    │ │ │  nonode@nohost
    │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ -{ok,<0.102.0>}
    │ │ │ -(dilbert@uab)3> node().
    │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ +{ok,<0.102.0>}
    │ │ │ +(dilbert@uab)3> node().
    │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/distributed_applications.html │ │ │ @@ -150,36 +150,36 @@ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ -  ]
    │ │ │ - }
    │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ +  ]
    │ │ │ + }
    │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │

    │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ application can be started by calling application:start(Application) at all │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ started as usual. That is, an application master is created and calls the │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │  > erl -sname cp2 -config cp2
    │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ @@ -187,30 +187,30 @@ │ │ │ │ │ │ Failover │ │ │

    │ │ │

    If the node where the application is running goes down, the application is │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ Included Applications). The application is then │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │

    │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ the node where a distributed application is running, the application is │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │
    │ │ │ │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/documentation.html │ │ │ @@ -112,23 +112,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Documentation │ │ │ │ │ │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ +attributes. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ attribute it documents. The │ │ │ attributes that can be documented are │ │ │ user-defined types │ │ │ (-type and -opaque) and │ │ │ behaviour module attributes │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ @@ -140,55 +140,55 @@ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │

    │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ --doc(#{since => "1.0"}).
    │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ +-doc(#{since => "1.0"}). │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ --doc #{since => "2.0"}.
    │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ +-doc #{since => "2.0"}. │ │ │ +add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ that only atoms are used for keys and │ │ │ strings for the values.

    │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │

    │ │ │

    The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ relative to the file where the -doc attribute is located. For example:

    %% doc/add.md
    │ │ │  Adds two numbers.

    and

    %% src/arith.erl
    │ │ │ --doc({file, "../doc/add.md"}).
    │ │ │ -add(One, Two) -> One + Two.

    │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ +add(One, Two) -> One + Two.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │

    │ │ │

    The module description should include details on how to use the API and examples │ │ │ of the different functions working together. Here is a good place to use images │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ an external page.

    The moduledoc attribute should start with a short paragraph describing the │ │ │ -module and then go into greater details. For example:

    -module(arith).
    │ │ │ +module and then go into greater details. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │     A module for basic arithmetic.
    │ │ │  
    │ │ │     This module can be used to add and subtract values. For example:
    │ │ │  
    │ │ │     ```erlang
    │ │ │     1> arith:substract(arith:add(2, 3), 1).
    │ │ │ @@ -203,94 +203,94 @@
    │ │ │  

    There are three reserved metadata keys for -moduledoc:

    • since - Shows in which version of the application the module was added. │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ or callback.
    • deprecated - Shows a text in the documentation explaining that it is │ │ │ deprecated and what to use instead.
    • format - The format to use for all documentation in this module. The │ │ │ default is text/markdown. It should be written using the │ │ │ mime type │ │ │ -of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ +of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │

    │ │ │

    Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ and then go into greater detail in needed.

    It is not recommended to include images or diagrams in this documentation as it │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

    For example:

    -doc """
    │ │ │  A number that can be used by the arith module.
    │ │ │  
    │ │ │  We use a special number here so that we know
    │ │ │  that this number comes from this module.
    │ │ │  """.
    │ │ │ --opaque number() :: {arith, erlang:number()}.
    │ │ │ +-opaque number() :: {arith, erlang:number()}.
    │ │ │  
    │ │ │  -doc """
    │ │ │  Adds two numbers.
    │ │ │  
    │ │ │  ### Example:
    │ │ │  
    │ │ │  ```
    │ │ │  1> arith:add(arith:number(1), arith:number(2)). {number, 3}
    │ │ │  ```
    │ │ │  """.
    │ │ │ --spec add(number(), number()) -> number().
    │ │ │ -add({number, One}, {number, Two}) -> {number, One + Two}.

    │ │ │ +-spec add(number(), number()) -> number(). │ │ │ +add({number, One}, {number, Two}) -> {number, One + Two}.

    │ │ │ │ │ │ │ │ │ │ │ │ Doc metadata │ │ │

    │ │ │

    There are four reserved metadata keys for -doc:

    • since => unicode:chardata() - Shows which version of the application the │ │ │ module was added.

    • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ function as deprecated.

    • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ another function in this module. The equivalence can be described using either │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ --spec add(One :: number(), Two :: number()) -> number().
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ the value converted to a string.

    • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ by the user.

    │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │

    │ │ │

    The doc signature is a short text shown to describe the function and its arguments. │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ --spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │ +-spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │  
    │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
    │ │ │ -sub(X, Y) -> X - Y.

    will have a signature of add(One, Two) and sub(One, Two).

    For types or callbacks, the signature is derived from the type or callback │ │ │ -specification. For example:

    -type number(Value) :: {number, Value}.
    │ │ │ +-spec sub(One :: integer(), Two :: integer()) -> integer().
    │ │ │ +sub(X, Y) -> X - Y.

    will have a signature of add(One, Two) and sub(One, Two).

    For types or callbacks, the signature is derived from the type or callback │ │ │ +specification. For example:

    -type number(Value) :: {number, Value}.
    │ │ │  %% signature will be `number(Value)`
    │ │ │  
    │ │ │ --opaque number() :: {number, number()}.
    │ │ │ +-opaque number() :: {number, number()}.
    │ │ │  %% signature will be `number()`
    │ │ │  
    │ │ │ --callback increment(In :: number()) -> Out.
    │ │ │ +-callback increment(In :: number()) -> Out.
    │ │ │  %% signature will be `increment(In)`
    │ │ │  
    │ │ │ --callback increment(In) -> Out when In :: number().
    │ │ │ +-callback increment(In) -> Out when In :: number().
    │ │ │  %% signature will be `increment(In)`

    If it is not possible to "easily" figure out a nice signature from the code, the │ │ │ MFA syntax is used instead. For example: add/2, number/1, increment/1

    It is possible to supply a custom signature by placing it as the first line of the │ │ │ -doc attribute. The provided signature must be in the form of a function │ │ │ declaration up until the ->. For example:

    -doc """
    │ │ │  add(One, Two)
    │ │ │  
    │ │ │  Adds two numbers.
    │ │ │  """.
    │ │ │ -add(A, B) -> A + B.

    Will create the signature add(One, Two). The signature will be removed from the │ │ │ +add(A, B) -> A + B.

    Will create the signature add(One, Two). The signature will be removed from the │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ callbacks.

    │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ @@ -375,21 +375,21 @@ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │

    │ │ │

    ExDoc has built-in support to generate │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ rebar3 project to use ExDoc to generate │ │ │ documentation add the following to your rebar3.config.

    %% Enable the plugin
    │ │ │ -{plugins, [rebar3_ex_doc]}.
    │ │ │ +{plugins, [rebar3_ex_doc]}.
    │ │ │  
    │ │ │ -{ex_doc, [
    │ │ │ -  {extras, ["README.md"]},
    │ │ │ -  {main, "README.md"},
    │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
    │ │ │ -]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ +{ex_doc, [ │ │ │ + {extras, ["README.md"]}, │ │ │ + {main, "README.md"}, │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ +]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ documentation to doc/index.html. For more details and options see │ │ │ the rebar3_ex_doc documentation.

    You can also download the │ │ │ release escript bundle from │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ is found by running ex_doc --help.

    If you are writing documentation that will be using │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ recommended to read its documentation.

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/drivers.html │ │ │ @@ -122,23 +122,23 @@ │ │ │ Drivers and Concurrency │ │ │ │ │ │

    The runtime system always takes a lock before running any code in a driver.

    By default, that lock is at the driver level, that is, if several ports have │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ running.

    A driver can be configured to have one lock for each port instead.

    If a driver is used in a functional way (that is, holds no state, but only does │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ -the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │ +the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
    │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
    │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │  
    │ │ │ -client_port() ->
    │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
    │ │ │ -	    ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ +client_port() -> │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ + ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ contention on the port lock for the driver.

    │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │

    │ │ │

    There are basically two ways to avoid copying a binary that is sent to a driver:

    • If the Data argument for port_control/3 is a │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_functions.html │ │ │ @@ -122,67 +122,67 @@ │ │ │ Pattern Matching │ │ │ │ │ │

      Pattern matching in function head as well as in case and receive clauses are │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ rearranging clauses.

      One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ binary last is usually slightly faster than placing it first.

      The following is a rather unnatural example to show another exception where │ │ │ -rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ -atom_map1(two) -> 2;
      │ │ │ -atom_map1(three) -> 3;
      │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map1(four) -> 4;
      │ │ │ -atom_map1(five) -> 5;
      │ │ │ -atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ +rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ +atom_map1(two) -> 2;
      │ │ │ +atom_map1(three) -> 3;
      │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ +atom_map1(four) -> 4;
      │ │ │ +atom_map1(five) -> 5;
      │ │ │ +atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ follows:

      • First, the input value is compared to one, two, and three (using a │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ there are many values) to select which one of the first three clauses to │ │ │ execute (if any).
      • If none of the first three clauses match, the fourth clause match as a │ │ │ variable always matches.
      • If the guard test is_integer(Int) succeeds, the fourth │ │ │ clause is executed.
      • If the guard test fails, the input value is compared to four, five, and │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ -exception if none of the values matched.)

      Rewriting to either:

      DO

      atom_map2(one) -> 1;
      │ │ │ -atom_map2(two) -> 2;
      │ │ │ -atom_map2(three) -> 3;
      │ │ │ -atom_map2(four) -> 4;
      │ │ │ -atom_map2(five) -> 5;
      │ │ │ -atom_map2(six) -> 6;
      │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

      or:

      DO

      atom_map3(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map3(one) -> 1;
      │ │ │ -atom_map3(two) -> 2;
      │ │ │ -atom_map3(three) -> 3;
      │ │ │ -atom_map3(four) -> 4;
      │ │ │ -atom_map3(five) -> 5;
      │ │ │ -atom_map3(six) -> 6.

      gives slightly more efficient matching code.

      Another example:

      DO NOT

      map_pairs1(_Map, [], Ys) ->
      │ │ │ +exception if none of the values matched.)

    Rewriting to either:

    DO

    atom_map2(one) -> 1;
    │ │ │ +atom_map2(two) -> 2;
    │ │ │ +atom_map2(three) -> 3;
    │ │ │ +atom_map2(four) -> 4;
    │ │ │ +atom_map2(five) -> 5;
    │ │ │ +atom_map2(six) -> 6;
    │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

    or:

    DO

    atom_map3(Int) when is_integer(Int) -> Int;
    │ │ │ +atom_map3(one) -> 1;
    │ │ │ +atom_map3(two) -> 2;
    │ │ │ +atom_map3(three) -> 3;
    │ │ │ +atom_map3(four) -> 4;
    │ │ │ +atom_map3(five) -> 5;
    │ │ │ +atom_map3(six) -> 6.

    gives slightly more efficient matching code.

    Another example:

    DO NOT

    map_pairs1(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs1(_Map, Xs, []) ->
    │ │ │ +map_pairs1(_Map, Xs, []) ->
    │ │ │      Xs;
    │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ the order written.

    If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ -clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │ +clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │      Xs;
    │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │      case Xs0 of
    │ │ │ -	[X|Xs] ->
    │ │ │ +	[X|Xs] ->
    │ │ │  	    case Ys0 of
    │ │ │ -		[Y|Ys] ->
    │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ -		[] ->
    │ │ │ +		[Y|Ys] ->
    │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ +		[] ->
    │ │ │  		    Xs0
    │ │ │  	    end;
    │ │ │ -	[] ->
    │ │ │ +	[] ->
    │ │ │  	    Ys0
    │ │ │      end.

    This is slightly faster for probably the most common case that the input lists │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ better type for the Xs variable.)

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_processes.html │ │ │ @@ -119,45 +119,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │

    │ │ │

    An Erlang process is lightweight compared to threads and processes in operating │ │ │ systems.

    A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ -as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │  #Fun<erl_eval.43.39164016>
    │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ -{memory,2616}
    │ │ │ -3> Bytes div erlang:system_info(wordsize).
    │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ +{memory,2616}
    │ │ │ +3> Bytes div erlang:system_info(wordsize).
    │ │ │  327

    The size includes 233 words for the heap area (which includes the stack). The │ │ │ garbage collector increases the heap as needed.

    The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ -stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │ +stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │    receive
    │ │ │ -     {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -     {From, Msg} ->
    │ │ │ -          Reply = handle_msg(Msg),
    │ │ │ +     {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +     {From, Msg} ->
    │ │ │ +          Reply = handle_msg(Msg),
    │ │ │            From ! Reply,
    │ │ │ -          loop()
    │ │ │ +          loop()
    │ │ │    end,
    │ │ │ -  io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ + io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ -correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │ +correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │     receive
    │ │ │ -      {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -      {From, Msg} ->
    │ │ │ -         Reply = handle_msg(Msg),
    │ │ │ +      {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +      {From, Msg} ->
    │ │ │ +         Reply = handle_msg(Msg),
    │ │ │           From ! Reply,
    │ │ │ -         loop()
    │ │ │ +         loop()
    │ │ │   end.

    │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │

    │ │ │

    The default initial heap size of 233 words is quite conservative to support │ │ │ @@ -189,30 +189,30 @@ │ │ │ │ │ │ │ │ │ Receiving messages │ │ │ │ │ │

    The cost of receiving messages depends on how complicated the receive │ │ │ expression is. A simple expression that matches any message is very cheap │ │ │ because it retrieves the first message in the message queue:

    DO

    receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.

    However, this is not always convenient: we can receive a message that we do not │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ expect:

    receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.

    While this is convenient it means that the entire message queue must be searched │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ long message queues, so there is an optimization for the common case of │ │ │ -sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ -Process ! {self(), MRef, Request},
    │ │ │ +sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ +Process ! {self(), MRef, Request},
    │ │ │  receive
    │ │ │ -    {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -        handle_error(Reason)
    │ │ │ +    {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +        handle_error(Reason)
    │ │ │  end.

    Since the compiler knows that the reference created by │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ arrived after the call to monitor/2.

    The above is a simple example where one is but guaranteed that the optimization │ │ │ will take, but what about more complicated code?

    │ │ │ │ │ │ @@ -228,101 +228,101 @@ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

    To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ example:

    %% DO
    │ │ │ -simple_receive() ->
    │ │ │ +simple_receive() ->
    │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
    │ │ │  receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
    │ │ │  %% cross_function_receive/0 further down.
    │ │ │ -selective_receive(Tag, Message) ->
    │ │ │ +selective_receive(Tag, Message) ->
    │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
    │ │ │  receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -optimized_receive(Process, Request) ->
    │ │ │ +optimized_receive(Process, Request) ->
    │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    MRef = monitor(process, Process),
    │ │ │ -    Process ! {self(), MRef, Request},
    │ │ │ +    MRef = monitor(process, Process),
    │ │ │ +    Process ! {self(), MRef, Request},
    │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
    │ │ │      receive
    │ │ │ -        {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -    handle_error(Reason)
    │ │ │ +        {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +    handle_error(Reason)
    │ │ │      end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -cross_function_receive() ->
    │ │ │ +cross_function_receive() ->
    │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    Ref = make_ref(),
    │ │ │ +    Ref = make_ref(),
    │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
    │ │ │ -    cross_function_receive(Ref).
    │ │ │ +    cross_function_receive(Ref).
    │ │ │  
    │ │ │ -cross_function_receive(Ref) ->
    │ │ │ +cross_function_receive(Ref) ->
    │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
    │ │ │      receive
    │ │ │ -        {Ref, Message} -> handle_msg(Message)
    │ │ │ +        {Ref, Message} -> handle_msg(Message)
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │

    │ │ │

    Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ -pool:

    DO

    days_in_month(M) ->
    │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ +pool:

    DO

    days_in_month(M) ->
    │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ unloaded in the future.

    When a literal is sent to another process, it is not copied. When a module │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ processes that hold references to that literal.

    There also exists a global literal pool that is managed by the │ │ │ persistent_term module.

    By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ for literals can be changed by using the │ │ │ +MIscs option when starting the emulator.

    Here is an example how the reserved virtual address space for literals can be │ │ │ raised to 2 GB (2048 MB):

    erl +MIscs 2048

    │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │

    │ │ │ -

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ -subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ -    kilo_byte(10, [42]).
    │ │ │ +

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ +subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ +    kilo_byte(10, [42]).
    │ │ │  
    │ │ │ -kilo_byte(0, Acc) ->
    │ │ │ +kilo_byte(0, Acc) ->
    │ │ │      Acc;
    │ │ │ -kilo_byte(N, Acc) ->
    │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ +kilo_byte(N, Acc) -> │ │ │ + kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

    1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
    │ │ │  1024

    Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ -requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │ +requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │  22

    Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ -been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │ +been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │  4094

    It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ -table:

    4> T = ets:new(tab, []).
    │ │ │ +table:

    4> T = ets:new(tab, []).
    │ │ │  #Ref<0.1662103692.2407923716.214181>
    │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │  true
    │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094
    │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094

    When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

    It is possible to build an experimental variant of the runtime system that │ │ │ will preserve sharing when copying terms by giving the │ │ │ --enable-sharing-preserving option to the configure script.

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/erl_interface.html │ │ │ @@ -120,119 +120,119 @@ │ │ │ to read the port example in Ports before reading this section.

    │ │ │ │ │ │ │ │ │ │ │ │ Erlang Program │ │ │

    │ │ │

    The following example shows an Erlang program communicating with a C program │ │ │ -over a plain port with home made encoding:

    -module(complex1).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +over a plain port with home made encoding:

    -module(complex1).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.
    │ │ │  
    │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │  
    │ │ │ -decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ +decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ example in Ports, using only the plain port:

    • As Erl_Interface operates on the Erlang external term format, the port must be │ │ │ set to use binaries.
    • Instead of inventing an encoding/decoding scheme, the │ │ │ term_to_binary/1 and │ │ │ -binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, decode(Data)}
    │ │ │ -end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, decode(Data)}
    │ │ │ +end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, binary_to_term(Data)}
    │ │ │ -end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, binary_to_term(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.

    Notice that calling complex2:foo/1 and complex2:bar/1 results in the tuple │ │ │ {foo,X} or {bar,Y} being sent to the complex process, which codes them as │ │ │ binaries and sends them to the port. This means that the C program must be able │ │ │ to handle these two tuples.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -362,27 +362,27 @@ │ │ │ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ │ │ │ complex.c erl_comm.c ei.c -lei -lpthread

    In Erlang/OTP R5B and later versions of OTP, the include and lib directories │ │ │ are situated under $OTPROOT/lib/erl_interface-VSN, where $OTPROOT is the │ │ │ root directory of the OTP installation (/usr/local/otp in the recent example) │ │ │ and VSN is the version of the Erl_interface application (3.2.1 in the recent │ │ │ example).

    In R4B and earlier versions of OTP, include and lib are situated under │ │ │ $OTPROOT/usr.

    Step 2. Start Erlang and compile the Erlang code:

    $ erl
    │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │  
    │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> c(complex2).
    │ │ │ -{ok,complex2}

    Step 3. Run the example:

    2> complex2:start("./extprg").
    │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> c(complex2).
    │ │ │ +{ok,complex2}

    Step 3. Run the example:

    2> complex2:start("./extprg").
    │ │ │  <0.34.0>
    │ │ │ -3> complex2:foo(3).
    │ │ │ +3> complex2:foo(3).
    │ │ │  4
    │ │ │ -4> complex2:bar(5).
    │ │ │ +4> complex2:bar(5).
    │ │ │  10
    │ │ │ -5> complex2:bar(352).
    │ │ │ +5> complex2:bar(352).
    │ │ │  704
    │ │ │ -6> complex2:stop().
    │ │ │ +6> complex2:stop().
    │ │ │  stop
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    logger_sasl_compatible to │ │ │ true. For more information, see │ │ │ SASL Error Logging in the SASL User's Guide.

    % erl -kernel logger_level info
    │ │ │ -Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    │ │ │ +Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    │ │ │  
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.916404 ===
    │ │ │      application: kernel
    │ │ │      started_at: nonode@nohost
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.922908 ===
    │ │ │      application: stdlib
    │ │ │      started_at: nonode@nohost
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.925755 ===
    │ │ │ -    supervisor: {local,kernel_safe_sup}
    │ │ │ -    started: [{pid,<0.74.0>},
    │ │ │ -              {id,disk_log_sup},
    │ │ │ -              {mfargs,{disk_log_sup,start_link,[]}},
    │ │ │ -              {restart_type,permanent},
    │ │ │ -              {shutdown,1000},
    │ │ │ -              {child_type,supervisor}]
    │ │ │ +    supervisor: {local,kernel_safe_sup}
    │ │ │ +    started: [{pid,<0.74.0>},
    │ │ │ +              {id,disk_log_sup},
    │ │ │ +              {mfargs,{disk_log_sup,start_link,[]}},
    │ │ │ +              {restart_type,permanent},
    │ │ │ +              {shutdown,1000},
    │ │ │ +              {child_type,supervisor}]
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.926056 ===
    │ │ │ -    supervisor: {local,kernel_safe_sup}
    │ │ │ -    started: [{pid,<0.75.0>},
    │ │ │ -              {id,disk_log_server},
    │ │ │ -              {mfargs,{disk_log_server,start_link,[]}},
    │ │ │ -              {restart_type,permanent},
    │ │ │ -              {shutdown,2000},
    │ │ │ -              {child_type,worker}]
    │ │ │ -Eshell V10.0  (abort with ^G)
    │ │ │ +    supervisor: {local,kernel_safe_sup}
    │ │ │ +    started: [{pid,<0.75.0>},
    │ │ │ +              {id,disk_log_server},
    │ │ │ +              {mfargs,{disk_log_server,start_link,[]}},
    │ │ │ +              {restart_type,permanent},
    │ │ │ +              {shutdown,2000},
    │ │ │ +              {child_type,worker}]
    │ │ │ +Eshell V10.0  (abort with ^G)
    │ │ │  1>
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ try expression can │ │ │ distinguish between the different classes, whereas the │ │ │ catch expression cannot. try and catch are described │ │ │ in Expressions.

    ClassOrigin
    errorRun-time error, for example, 1+a, or the process called error/1
    exitThe process called exit/1
    throwThe process called throw/1

    Table: Exception Classes.

    All of the above exceptions can also be generated by calling erlang:raise/3.

    An exception consists of its class, an exit reason (see │ │ │ Exit Reason), and a stack trace (which aids in finding │ │ │ the code location of the exception).

    The stack trace can be bound to a variable from within a try expression for │ │ │ any exception class, or as part of the exit reason when a run-time error is │ │ │ -caught by a catch. Example:

    > {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
    │ │ │ -[{shell,apply_fun,3,[]},
    │ │ │ - {erl_eval,do_apply,6,[]},
    │ │ │ - ...]
    │ │ │ -> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
    │ │ │ -[{shell,apply_fun,3,[]},
    │ │ │ - {erl_eval,do_apply,6,[]},
    │ │ │ - ...]

    │ │ │ +caught by a catch. Example:

    > {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
    │ │ │ +[{shell,apply_fun,3,[]},
    │ │ │ + {erl_eval,do_apply,6,[]},
    │ │ │ + ...]
    │ │ │ +> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
    │ │ │ +[{shell,apply_fun,3,[]},
    │ │ │ + {erl_eval,do_apply,6,[]},
    │ │ │ + ...]

    │ │ │ │ │ │ │ │ │ │ │ │ The call-stack back trace (stacktrace) │ │ │

    │ │ │

    The stack back-trace (stacktrace) is a list that │ │ │ contains {Module, Function, Arity, ExtraInfo} and/or {Fun, Arity, ExtraInfo} │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/events.html │ │ │ @@ -135,43 +135,43 @@ │ │ │ event handler.

    │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │

    │ │ │

    The callback module for the event handler writing error messages to the terminal │ │ │ -can look as follows:

    -module(terminal_logger).
    │ │ │ --behaviour(gen_event).
    │ │ │ +can look as follows:

    -module(terminal_logger).
    │ │ │ +-behaviour(gen_event).
    │ │ │  
    │ │ │ --export([init/1, handle_event/2, terminate/2]).
    │ │ │ +-export([init/1, handle_event/2, terminate/2]).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, []}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, []}.
    │ │ │  
    │ │ │ -handle_event(ErrorMsg, State) ->
    │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, State}.
    │ │ │ +handle_event(ErrorMsg, State) ->
    │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, State}.
    │ │ │  
    │ │ │ -terminate(_Args, _State) ->
    │ │ │ +terminate(_Args, _State) ->
    │ │ │      ok.

    The callback module for the event handler writing error messages to a file can │ │ │ -look as follows:

    -module(file_logger).
    │ │ │ --behaviour(gen_event).
    │ │ │ +look as follows:

    -module(file_logger).
    │ │ │ +-behaviour(gen_event).
    │ │ │  
    │ │ │ --export([init/1, handle_event/2, terminate/2]).
    │ │ │ +-export([init/1, handle_event/2, terminate/2]).
    │ │ │  
    │ │ │ -init(File) ->
    │ │ │ -    {ok, Fd} = file:open(File, read),
    │ │ │ -    {ok, Fd}.
    │ │ │ -
    │ │ │ -handle_event(ErrorMsg, Fd) ->
    │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, Fd}.
    │ │ │ +init(File) ->
    │ │ │ +    {ok, Fd} = file:open(File, read),
    │ │ │ +    {ok, Fd}.
    │ │ │ +
    │ │ │ +handle_event(ErrorMsg, Fd) ->
    │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, Fd}.
    │ │ │  
    │ │ │ -terminate(_Args, Fd) ->
    │ │ │ -    file:close(Fd).

    The code is explained in the next sections.

    │ │ │ +terminate(_Args, Fd) -> │ │ │ + file:close(Fd).

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting an Event Manager │ │ │

    │ │ │

    To start an event manager for handling errors, as described in the previous │ │ │ example, call the following function:

    gen_event:start_link({local, error_man})

    gen_event:start_link/1 spawns and links to a new event manager process.

    The argument, {local, error_man}, specifies the name under which the │ │ │ @@ -184,57 +184,57 @@ │ │ │ manager that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding an Event Handler │ │ │

    │ │ │

    The following example shows how to start an event manager and add an event │ │ │ -handler to it by using the shell:

    1> gen_event:start({local, error_man}).
    │ │ │ -{ok,<0.31.0>}
    │ │ │ -2> gen_event:add_handler(error_man, terminal_logger, []).
    │ │ │ +handler to it by using the shell:

    1> gen_event:start({local, error_man}).
    │ │ │ +{ok,<0.31.0>}
    │ │ │ +2> gen_event:add_handler(error_man, terminal_logger, []).
    │ │ │  ok

    This function sends a message to the event manager registered as error_man, │ │ │ telling it to add the event handler terminal_logger. The event manager calls │ │ │ the callback function terminal_logger:init([]), where the argument [] is the │ │ │ third argument to add_handler. init/1 is expected to return {ok, State}, │ │ │ -where State is the internal state of the event handler.

    init(_Args) ->
    │ │ │ -    {ok, []}.

    Here, init/1 does not need any input data and ignores its argument. For │ │ │ +where State is the internal state of the event handler.

    init(_Args) ->
    │ │ │ +    {ok, []}.

    Here, init/1 does not need any input data and ignores its argument. For │ │ │ terminal_logger, the internal state is not used. For file_logger, the │ │ │ -internal state is used to save the open file descriptor.

    init(File) ->
    │ │ │ -    {ok, Fd} = file:open(File, read),
    │ │ │ -    {ok, Fd}.

    │ │ │ +internal state is used to save the open file descriptor.

    init(File) ->
    │ │ │ +    {ok, Fd} = file:open(File, read),
    │ │ │ +    {ok, Fd}.

    │ │ │ │ │ │ │ │ │ │ │ │ Notifying about Events │ │ │

    │ │ │
    3> gen_event:notify(error_man, no_reply).
    │ │ │  ***Error*** no_reply
    │ │ │  ok

    error_man is the name of the event manager and no_reply is the event.

    The event is made into a message and sent to the event manager. When the event │ │ │ is received, the event manager calls handle_event(Event, State) for each │ │ │ installed event handler, in the same order as they were added. The function is │ │ │ expected to return a tuple {ok,State1}, where State1 is a new value for the │ │ │ -state of the event handler.

    In terminal_logger:

    handle_event(ErrorMsg, State) ->
    │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, State}.

    In file_logger:

    handle_event(ErrorMsg, Fd) ->
    │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, Fd}.

    │ │ │ +state of the event handler.

    In terminal_logger:

    handle_event(ErrorMsg, State) ->
    │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, State}.

    In file_logger:

    handle_event(ErrorMsg, Fd) ->
    │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, Fd}.

    │ │ │ │ │ │ │ │ │ │ │ │ Deleting an Event Handler │ │ │

    │ │ │ -
    4> gen_event:delete_handler(error_man, terminal_logger, []).
    │ │ │ +
    4> gen_event:delete_handler(error_man, terminal_logger, []).
    │ │ │  ok

    This function sends a message to the event manager registered as error_man, │ │ │ telling it to delete the event handler terminal_logger. The event manager │ │ │ calls the callback function terminal_logger:terminate([], State), where the │ │ │ argument [] is the third argument to delete_handler. terminate/2 is to be │ │ │ the opposite of init/1 and do any necessary cleaning up. Its return value is │ │ │ -ignored.

    For terminal_logger, no cleaning up is necessary:

    terminate(_Args, _State) ->
    │ │ │ -    ok.

    For file_logger, the file descriptor opened in init must be closed:

    terminate(_Args, Fd) ->
    │ │ │ -    file:close(Fd).

    │ │ │ +ignored.

    For terminal_logger, no cleaning up is necessary:

    terminate(_Args, _State) ->
    │ │ │ +    ok.

    For file_logger, the file descriptor opened in init must be closed:

    terminate(_Args, Fd) ->
    │ │ │ +    file:close(Fd).

    │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

    │ │ │

    When an event manager is stopped, it gives each of the installed event handlers │ │ │ the chance to clean up by calling terminate/2, the same way as when deleting a │ │ │ @@ -249,33 +249,33 @@ │ │ │ this is done is defined by a shutdown strategy set in │ │ │ the supervisor.

    │ │ │ │ │ │ │ │ │ │ │ │ Standalone Event Managers │ │ │

    │ │ │ -

    An event manager can also be stopped by calling:

    1> gen_event:stop(error_man).
    │ │ │ +

    An event manager can also be stopped by calling:

    1> gen_event:stop(error_man).
    │ │ │  ok

    │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │

    │ │ │

    If the gen_event process is to be able to receive other messages │ │ │ than events, the callback function handle_info(Info, State) must be │ │ │ implemented to handle them. Examples of other messages are exit │ │ │ messages if the event manager is linked to other processes than the │ │ │ supervisor (for example via gen_event:add_sup_handler/3) and is │ │ │ -trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ +trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │      %% Code to handle exits here.
    │ │ │      ...
    │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │      %% Code to convert state (and more) during code change.
    │ │ │      ...
    │ │ │ -    {ok, NewState}.
    │ │ │ +
    {ok, NewState}.
    │ │ │
    │ │ │ │ │ │

    pattern matching. Erlang uses │ │ │ single assignment, that is, a variable can only be bound once.

    The anonymous variable is denoted by underscore (_) and can be used when a │ │ │ variable is required but its value can be ignored.

    Example:

    [H|_] = [1,2,3]

    Variables starting with underscore (_), for example, _Height, are normal │ │ │ variables, not anonymous. However, they are ignored by the compiler in the sense │ │ │ -that they do not generate warnings.

    Example:

    The following code:

    member(_, []) ->
    │ │ │ -    [].

    can be rewritten to be more readable:

    member(Elem, []) ->
    │ │ │ -    [].

    This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ -the code can be rewritten to:

    member(_Elem, []) ->
    │ │ │ -    [].

    Notice that since variables starting with an underscore are not anonymous, the │ │ │ -following example matches:

    {_,_} = {1,2}

    But this example fails:

    {_N,_N} = {1,2}

    The scope for a variable is its function clause. Variables bound in a branch of │ │ │ +that they do not generate warnings.

    Example:

    The following code:

    member(_, []) ->
    │ │ │ +    [].

    can be rewritten to be more readable:

    member(Elem, []) ->
    │ │ │ +    [].

    This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ +the code can be rewritten to:

    member(_Elem, []) ->
    │ │ │ +    [].

    Notice that since variables starting with an underscore are not anonymous, the │ │ │ +following example matches:

    {_,_} = {1,2}

    But this example fails:

    {_N,_N} = {1,2}

    The scope for a variable is its function clause. Variables bound in a branch of │ │ │ an if, case, or receive expression must be bound in all branches to have a │ │ │ value outside the expression. Otherwise they are regarded as unsafe outside │ │ │ the expression.

    For the try expression variable scoping is limited so that variables bound in │ │ │ the expression are always unsafe outside the expression.

    │ │ │ │ │ │ │ │ │ │ │ │ Patterns │ │ │

    │ │ │

    A pattern has the same structure as a term but can contain unbound variables.

    Example:

    Name1
    │ │ │ -[H|T]
    │ │ │ -{error,Reason}

    Patterns are allowed in clause heads, case expressions, │ │ │ +[H|T] │ │ │ +{error,Reason}

    Patterns are allowed in clause heads, case expressions, │ │ │ receive expressions, and │ │ │ match expressions.

    │ │ │ │ │ │ │ │ │ │ │ │ The Compound Pattern Operator │ │ │

    │ │ │

    If Pattern1 and Pattern2 are valid patterns, the following is also a valid │ │ │ pattern:

    Pattern1 = Pattern2

    When matched against a term, both Pattern1 and Pattern2 are matched against │ │ │ -the term. The idea behind this feature is to avoid reconstruction of terms.

    Example:

    f({connect,From,To,Number,Options}, To) ->
    │ │ │ -    Signal = {connect,From,To,Number,Options},
    │ │ │ +the term. The idea behind this feature is to avoid reconstruction of terms.

    Example:

    f({connect,From,To,Number,Options}, To) ->
    │ │ │ +    Signal = {connect,From,To,Number,Options},
    │ │ │      ...;
    │ │ │ -f(Signal, To) ->
    │ │ │ -    ignore.

    can instead be written as

    f({connect,_,To,_,_} = Signal, To) ->
    │ │ │ +f(Signal, To) ->
    │ │ │ +    ignore.

    can instead be written as

    f({connect,_,To,_,_} = Signal, To) ->
    │ │ │      ...;
    │ │ │ -f(Signal, To) ->
    │ │ │ +f(Signal, To) ->
    │ │ │      ignore.

    The compound pattern operator does not imply that its operands are matched in │ │ │ any particular order. That means that it is not legal to bind a variable in │ │ │ Pattern1 and use it in Pattern2, or vice versa.

    │ │ │ │ │ │ │ │ │ │ │ │ String Prefix in Patterns │ │ │

    │ │ │ -

    When matching strings, the following is a valid pattern:

    f("prefix" ++ Str) -> ...

    This is syntactic sugar for the equivalent, but harder to read:

    f([$p,$r,$e,$f,$i,$x | Str]) -> ...

    │ │ │ +

    When matching strings, the following is a valid pattern:

    f("prefix" ++ Str) -> ...

    This is syntactic sugar for the equivalent, but harder to read:

    f([$p,$r,$e,$f,$i,$x | Str]) -> ...

    │ │ │ │ │ │ │ │ │ │ │ │ Expressions in Patterns │ │ │

    │ │ │

    An arithmetic expression can be used within a pattern if it meets both of the │ │ │ -following two conditions:

    • It uses only numeric or bitwise operators.
    • Its value can be evaluated to a constant when complied.

    Example:

    case {Value, Result} of
    │ │ │ -    {?THRESHOLD+1, ok} -> ...

    │ │ │ +following two conditions:

    • It uses only numeric or bitwise operators.
    • Its value can be evaluated to a constant when complied.

    Example:

    case {Value, Result} of
    │ │ │ +    {?THRESHOLD+1, ok} -> ...

    │ │ │ │ │ │ │ │ │ │ │ │ The Match Operator │ │ │

    │ │ │

    The following matches Pattern against Expr:

    Pattern = Expr

    If the matching succeeds, any unbound variable in the pattern becomes bound and │ │ │ the value of Expr is returned.

    If multiple match operators are applied in sequence, they will be evaluated from │ │ │ -right to left.

    If the matching fails, a badmatch run-time error occurs.

    Examples:

    1> {A, B} = T = {answer, 42}.
    │ │ │ -{answer,42}
    │ │ │ +right to left.

    If the matching fails, a badmatch run-time error occurs.

    Examples:

    1> {A, B} = T = {answer, 42}.
    │ │ │ +{answer,42}
    │ │ │  2> A.
    │ │ │  answer
    │ │ │  3> B.
    │ │ │  42
    │ │ │  4> T.
    │ │ │ -{answer,42}
    │ │ │ -5> {C, D} = [1, 2].
    │ │ │ +{answer,42}
    │ │ │ +5> {C, D} = [1, 2].
    │ │ │  ** exception error: no match of right-hand side value [1,2]

    Because multiple match operators are evaluated from right to left, it means │ │ │ that:

    Pattern1 = Pattern2 = . . . = PatternN = Expression

    is equivalent to:

    Temporary = Expression,
    │ │ │  PatternN = Temporary,
    │ │ │     .
    │ │ │     .
    │ │ │     .,
    │ │ │  Pattern2 = Temporary,
    │ │ │ @@ -239,30 +239,30 @@
    │ │ │  can safely be skipped on a first reading.

    The = character is used to denote two similar but distinct operators: the │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ determined by context.

    The compound pattern operator is used to construct a compound pattern from two │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ -same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │ +same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │  * 1:7: variable 'Key' is unbound
    │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
    │ │ │ -{{1,2},3}
    │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
    │ │ │ -{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ +{{1,2},3} │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ +{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ -are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ -#{key => key2,key2 => value}
    │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │ +are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ +#{key => key2,key2 => value}
    │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │  value
    │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │  value
    │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │  * 1:12: variable 'Key' is unbound
    │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │  42

    The expression at prompt 2> first matches the value of variable M against │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ variable Value.

    The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ @@ -276,30 +276,30 @@ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

    │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │

    │ │ │ -
    ExprF(Expr1,...,ExprN)
    │ │ │ -ExprM:ExprF(Expr1,...,ExprN)

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ +

    ExprF(Expr1,...,ExprN)
    │ │ │ +ExprM:ExprF(Expr1,...,ExprN)

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ -This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ +This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ an atom or evaluate to a fun.

    If ExprF is an atom, the function is said to be called by using the │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ -imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ -spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ -Fun1(3).
    │ │ │ +imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ +spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ +Fun1(3).
    │ │ │  4
    │ │ │ -2> fun lists:append/2([1,2], [3,4]).
    │ │ │ -[1,2,3,4]
    │ │ │ +2> fun lists:append/2([1,2], [3,4]).
    │ │ │ +[1,2,3,4]
    │ │ │  3>

    Notice that when calling a local function, there is a difference between using │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ latest version of the module. See │ │ │ Compilation and Code Loading and │ │ │ Function Evaluation.

    │ │ │ │ │ │ │ │ │ @@ -316,40 +316,40 @@ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ auto-imported BIFs do not silently change the behavior of old code.

    However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ -you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │ +you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ -length([]) ->
    │ │ │ +length([]) ->
    │ │ │      0;
    │ │ │ -length([H|T]) ->
    │ │ │ -    1 + length(T). %% Calls the local function length/1
    │ │ │ +length([H|T]) ->
    │ │ │ +    1 + length(T). %% Calls the local function length/1
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │                                    %% which is allowed in guards
    │ │ │      long.

    The same logic applies to explicitly imported functions from other modules, as │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ -another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │ +another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ --import(mod,[length/1]).
    │ │ │ +-import(mod,[length/1]).
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │                                     %% which is allowed in guards
    │ │ │  
    │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │  
    │ │ │ -f(X) ->
    │ │ │ -    length(X).                     %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ +f(X) -> │ │ │ + length(X). %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ qualified function name.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -361,40 +361,40 @@ │ │ │ ...; │ │ │ GuardSeqN -> │ │ │ BodyN │ │ │ end

    The branches of an if-expression are scanned sequentially until a guard │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

    The return value of Body is the return value of the if expression.

    If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ -that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │ +that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │      if
    │ │ │          X > Y ->
    │ │ │              true;
    │ │ │          true -> % works as an 'else' branch
    │ │ │              false
    │ │ │      end

    │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │

    │ │ │
    case Expr of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ GuardSeq is true, the corresponding Body is evaluated.

    The return value of Body is the return value of the case expression.

    If there is no matching pattern with a true guard sequence, a case_clause │ │ │ -run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │ +run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │      case Signal of
    │ │ │ -        {signal, _What, _From, _To} ->
    │ │ │ +        {signal, _What, _From, _To} ->
    │ │ │              true;
    │ │ │ -        {signal, _What, _To} ->
    │ │ │ +        {signal, _What, _To} ->
    │ │ │              true;
    │ │ │          _Else ->
    │ │ │              false
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -412,57 +412,57 @@ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ block is Expr2.

    None of the variables bound in a maybe block must be used in the code that │ │ │ follows the block.

    Here is an example:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  end

    Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

    Now let us assume that a() returns error. The conditional match operator in │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ b() returns wrong, the return value of the maybe block is wrong.

    Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ -expression fails to match the pattern.

    The example can be written in a less succient way using nested case expressions:

    case a() of
    │ │ │ -    {ok, A} ->
    │ │ │ +expression fails to match the pattern.

    The example can be written in a less succient way using nested case expressions:

    case a() of
    │ │ │ +    {ok, A} ->
    │ │ │          true = A >= 0,
    │ │ │ -        case b() of
    │ │ │ -            {ok, B} ->
    │ │ │ +        case b() of
    │ │ │ +            {ok, B} ->
    │ │ │                  A + B;
    │ │ │              Other1 ->
    │ │ │                  Other1
    │ │ │          end;
    │ │ │      Other2 ->
    │ │ │          Other2
    │ │ │  end

    The maybe block can be augmented with else clauses:

    maybe
    │ │ │      Expr1,
    │ │ │      ...,
    │ │ │      ExprN
    │ │ │  else
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    If a conditional match operator fails, the failed expression is matched against │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ maybe block.

    If there is no matching pattern with a true guard sequence, an else_clause │ │ │ run-time error occurs.

    None of the variables bound in a maybe block must be used in the else │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ code that follows the maybe block.

    Here is the previous example augmented with else clauses:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  else
    │ │ │      error -> error;
    │ │ │      wrong -> error
    │ │ │  end

    The else clauses translate the failing value from the conditional match │ │ │ operators to the value error. If the failing value is not one of the │ │ │ recognized values, a else_clause run-time error occurs.

    │ │ │ @@ -481,75 +481,75 @@ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

    │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │

    │ │ │
    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    Fetches a received message present in the message queue of the process. The │ │ │ first message in the message queue is matched sequentially against the patterns │ │ │ from top to bottom. If no match was found, the matching sequence is repeated for │ │ │ the second message in the queue, and so on. Messages are queued in the │ │ │ order they were received. If a match │ │ │ succeeds, that is, if the Pattern matches and the optional guard sequence │ │ │ GuardSeq is true, then the message is removed from the message queue and the │ │ │ corresponding Body is evaluated. All other messages in the message queue │ │ │ remain unchanged.

    The return value of Body is the return value of the receive expression.

    receive never fails. The execution is suspended, possibly indefinitely, until │ │ │ a message arrives that matches one of the patterns and with a true guard │ │ │ -sequence.

    Example:

    wait_for_onhook() ->
    │ │ │ +sequence.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      end.

    The receive expression can be augmented with a timeout:

    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    receive...after works exactly as receive, except that if no matching message │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ expression. ExprT is to evaluate to an integer, or the atom infinity. The │ │ │ allowed integer range is from 0 to 4294967295, that is, the longest possible │ │ │ timeout is almost 50 days. With a zero value the timeout occurs immediately if │ │ │ there is no matching message in the message queue.

    The atom infinity will make the process wait indefinitely for a matching │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ -values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │ +values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      after
    │ │ │          60000 ->
    │ │ │ -            disconnect(),
    │ │ │ -            error()
    │ │ │ +            disconnect(),
    │ │ │ +            error()
    │ │ │      end.

    It is legal to use a receive...after expression with no branches:

    receive
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    This construction does not consume any messages, only suspends execution in the │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ -    spawn(m, timer, [self()]).
    │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ +    spawn(m, timer, [self()]).
    │ │ │  
    │ │ │ -timer(Pid) ->
    │ │ │ +timer(Pid) ->
    │ │ │      receive
    │ │ │      after
    │ │ │          5000 ->
    │ │ │              Pid ! timeout
    │ │ │      end.

    For more information on timers in Erlang in general, see the │ │ │ Timers section of the │ │ │ Time and Time Correction in Erlang │ │ │ @@ -591,21 +591,21 @@ │ │ │ false │ │ │ 4> 0.0 =:= -0.0. │ │ │ false │ │ │ 5> 0.0 =:= +0.0. │ │ │ true │ │ │ 6> 1 > a. │ │ │ false │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ false │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ true │ │ │ -9> <<2:2>> < <<128>>. │ │ │ +9> <<2:2>> < <<128>>. │ │ │ true │ │ │ -10> <<3:2>> < <<128>>. │ │ │ +10> <<3:2>> < <<128>>. │ │ │ false

    Note

    Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ and -0.0 to be the same term.

    This was changed in OTP 27 but legacy code may have expected them to be │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ in a term equivalence test.

    If you need to match 0.0 specifically, the warning can be silenced by │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ interpret the match as being done on purpose.

    │ │ │ @@ -631,15 +631,15 @@ │ │ │ 0 │ │ │ 8> 2#10 bor 2#01. │ │ │ 3 │ │ │ 9> a + 10. │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ in operator +/2 │ │ │ called as a + 10 │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ ** exception error: a system limit has been reached │ │ │ in operator bsl/2 │ │ │ called as 1 bsl 18446744073709551616

    │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ @@ -658,136 +658,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │

    │ │ │
    Expr1 orelse Expr2
    │ │ │  Expr1 andalso Expr2

    Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

    • Expr1 evaluates to false in an orelse expression.

    or

    • Expr1 evaluates to true in an andalso expression.

    Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ -Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ -never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ -         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ -andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ -all(_, []) ->
    │ │ │ +Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ +never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ +         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ +andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ +all(_, []) ->
    │ │ │      true.

    Change

    Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ and as consequence, andalso and orelse were not tail-recursive.

    │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │

    │ │ │
    Expr1 ++ Expr2
    │ │ │  Expr1 -- Expr2

    The list concatenation operator ++ appends its second argument to its first │ │ │ and returns the resulting list.

    The list subtraction operator -- produces a list that is a copy of the first │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ -the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ -[1,2,3,4,5]
    │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ -[3,1,2]

    │ │ │ +the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ +[1,2,3,4,5]
    │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ +[3,1,2]

    │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │

    │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │

    │ │ │

    Constructing a new map is done by letting an expression K be associated with │ │ │ -another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ -association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ +another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ +association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ then the resulting terms are used as key and value respectively.

    Keys and values are separated by the => arrow and associations are separated │ │ │ -by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ -M3 = #{k => {A,B}},       % single association with variables
    │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ -map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ -#{1 => b }
    │ │ │ -2> #{1.0 => a, 1 => b}.
    │ │ │ -#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ +by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ +M3 = #{k => {A,B}},       % single association with variables
    │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ +map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ +#{1 => b }
    │ │ │ +2> #{1.0 => a, 1 => b}.
    │ │ │ +#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ two matching keys.

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │

    │ │ │

    Updating a map has a similar syntax as constructing it.

    An expression defining the map to be updated is put in front of the expression │ │ │ -defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ +defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ created from key K to value V.

    If key K matches an existing key in map M, its associated value is replaced │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ -map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ +map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ that evaluates to an existing key in M.

    If key K does not match any existing keys in map M, an exception of type │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ -expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ -M1 = M0#{a => 0},
    │ │ │ -M2 = M1#{a => 1, b => 2},
    │ │ │ -M3 = M2#{"function" => fun() -> f() end},
    │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ -#{1 => a }
    │ │ │ -2> M#{1.0 => b}.
    │ │ │ -#{1 => a, 1.0 => b}.
    │ │ │ -3> M#{1 := b}.
    │ │ │ -#{1 => b}
    │ │ │ -4> M#{1.0 := b}.
    │ │ │ +expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ +M1 = M0#{a => 0},
    │ │ │ +M2 = M1#{a => 1, b => 2},
    │ │ │ +M3 = M2#{"function" => fun() -> f() end},
    │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ +#{1 => a }
    │ │ │ +2> M#{1.0 => b}.
    │ │ │ +#{1 => a, 1.0 => b}.
    │ │ │ +3> M#{1 := b}.
    │ │ │ +#{1 => b}
    │ │ │ +4> M#{1.0 := b}.
    │ │ │  ** exception error: bad argument

    As in construction, the order in which the key and value expressions are │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ case, the latter value is used.

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │

    │ │ │ -

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ +

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ guard expression, with all variables already │ │ │ bound. V can be any pattern with either bound or unbound variables.

    If the variable V is unbound, it becomes bound to the value associated with │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ must match the value associated with K in M.

    Change

    Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ -either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ -2> #{"tuple" := {1,B}} = M.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ +either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │ +2> #{"tuple" := {1,B}} = M.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │  3> B.
    │ │ │ -2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ +2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ associated values of their respective keys.

    If the matching conditions are not met the match fails.

    Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ as a delimiter for the associations.

    The order in which keys are declared in matching has no relevance.

    Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ -keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ -fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
    │ │ │ +keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ +fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := start}};
    │ │ │ +    {reply, ok, S#{state := start}};
    │ │ │  
    │ │ │  %% only change if started
    │ │ │ -handle_call(change, From, #{state := start} = S) ->
    │ │ │ +handle_call(change, From, #{state := start} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := changed}};

    │ │ │ + {reply, ok, S#{state := changed}};

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │

    │ │ │

    Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ expressions.

    The following guard BIFs handle maps:

    │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │

    │ │ │

    The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ -ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ -<<E1>>
    │ │ │ -<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ +ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ +<<E1>>
    │ │ │ +<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ of the bit string.

    Each segment specification Ei is a value, whose default type is integer, │ │ │ followed by an optional size expression and an optional type specifier list.

    Ei = Value |
    │ │ │       Value:Size |
    │ │ │       Value/TypeSpecifierList |
    │ │ │       Value:Size/TypeSpecifierList

    When used in a bit string construction, Value is an expression that is to │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ @@ -798,34 +798,34 @@ │ │ │ guard expression that evaluates to an │ │ │ integer. All variables in the guard expression must be already bound.

    Change

    Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ bound to an integer.

    The value of Size specifies the size of the segment in units (see below). The │ │ │ default value depends on the type (see below):

    • For integer it is 8.
    • For float it is 64.
    • For binary and bitstring it is the whole binary or bit string.

    In matching, the default value for a binary or bit string segment is only valid │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ must have a size specification.

    Binaries

    A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ which is the most common and useful type of bit string.

    A binary has a canonical representation in memory. Here follows a sequence of │ │ │ -bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ -information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │ +bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ +information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
    │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ -<<"abcde">>
    │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ +<<"abcde">>
    │ │ │  3> A.
    │ │ │ -<<"abc">>
    │ │ │ +<<"abc">>
    │ │ │  4> B.
    │ │ │ -<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ +<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ of the segment is implicitly determined by the type and value itself.

    TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

    • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ for more information about the utf types.

    • Signedness= signed | unsigned - Only matters for matching and when │ │ │ the type is integer. The default is unsigned.

    • Endianness= big | little | native - Specifies byte level (octet │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ -default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ +default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ for the types utf8, utf16, and utf32.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -850,41 +850,41 @@ │ │ │ │ │ │ Binary segments │ │ │

    │ │ │

    In this section, the phrase "binary segment" refers to any one of the segment │ │ │ types binary, bitstring, bytes, and bits.

    See also the paragraphs about Binaries.

    When constructing binaries and no size is specified for a binary segment, the │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ -the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ -<<"abc">>
    │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ -<<"abc">>
    │ │ │ -3> <<(<<"abc">>)/binary>>.
    │ │ │ -<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ +the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ +<<"abc">>
    │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ +<<"abc">>
    │ │ │ +3> <<(<<"abc">>)/binary>>.
    │ │ │ +<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ segment has a unit value of 8.

    Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ -8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ -** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ -<<1:1>>
    │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ -<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ +8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ +** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ +<<1:1>>
    │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ +<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ -divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ -<<>>
    │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │ +divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ +<<>>
    │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │  ** exception error: no match of right hand side value <<"a">>
    │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ -<<"ab">>
    │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ +<<"ab">>
    │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │  ** exception error: no match of right hand side value <<"abc">>
    │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
    │ │ │ -<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ +<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

    When constructing binaries, the size of the binary being interpolated into the │ │ │ -constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ -<<"ab">>
    │ │ │ -2> <<(<<"a">>):2/binary>>.
    │ │ │ +constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ +<<"ab">>
    │ │ │ +2> <<(<<"a">>):2/binary>>.
    │ │ │  ** exception error: construction of binary failed
    │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

    │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │

    │ │ │ @@ -900,78 +900,78 @@ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ returned value falls outside those ranges.

    A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ Unicode standard.)

    A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

    A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ -is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ -<<1,17,42>>
    │ │ │ -2> Bin2 = <<"abc">>.
    │ │ │ -<<97,98,99>>
    │ │ │ +is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ +<<1,17,42>>
    │ │ │ +2> Bin2 = <<"abc">>.
    │ │ │ +<<97,98,99>>
    │ │ │  
    │ │ │ -3> Bin3 = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +3> Bin3 = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  5> C.
    │ │ │  42
    │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  7> D.
    │ │ │  273
    │ │ │  8> F.
    │ │ │  42
    │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  10> H.
    │ │ │ -<<17,0,42>>
    │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ -<<1,17,2,10:4>>
    │ │ │ +<<17,0,42>>
    │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ +<<1,17,2,10:4>>
    │ │ │  12> J.
    │ │ │ -<<17,2,10:4>>
    │ │ │ +<<17,2,10:4>>
    │ │ │  
    │ │ │ -13> <<1024/utf8>>.
    │ │ │ -<<208,128>>
    │ │ │ +13> <<1024/utf8>>.
    │ │ │ +<<208,128>>
    │ │ │  
    │ │ │ -14> <<1:1,0:7>>.
    │ │ │ -<<128>>
    │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
    │ │ │ -<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ +14> <<1:1,0:7>>. │ │ │ +<<128>> │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ +<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │

    │ │ │
    fun
    │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │                Body1;
    │ │ │      ...;
    │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │                BodyK
    │ │ │  end

    A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ Between them is to be a function declaration, similar to a │ │ │ regular function declaration, │ │ │ except that the function name is optional and is to be a variable, if any.

    Variables in a fun head shadow the function name and both shadow variables in │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ -body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3
    │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -4> Fun2(7).
    │ │ │ +4> Fun2(7).
    │ │ │  gt
    │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -6> Fun3(4).
    │ │ │ +6> Fun3(4).
    │ │ │  24

    The following fun expressions are also allowed:

    fun Name/Arity
    │ │ │  fun Module:Name/Arity

    In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ -specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ +specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ code for the module in which it is defined.

    Change

    Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ variables.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ @@ -981,35 +981,35 @@ │ │ │
    catch Expr

    Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ exception:

    Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ recent function calls, see Exit Reasons.

    Examples:

    1> catch 1+2.
    │ │ │  3
    │ │ │  2> catch 1+a.
    │ │ │ -{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │ +{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │  hello

    If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ error occurs.

    Change

    Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ -it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │ +it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    Starting from Erlang/OTP 24, the parentheses can be omitted:

    1> A = catch 42.
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │

    │ │ │
    try Exprs
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    This is an enhancement of catch. It gives the │ │ │ possibility to:

    • Distinguish between different exception classes.
    • Choose to handle only the desired ones.
    • Passing the others on to an enclosing try or catch, or to default error │ │ │ handling.

    Notice that although the keyword catch is used in the try expression, there │ │ │ is not a catch expression within the try expression.

    It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ @@ -1019,47 +1019,47 @@ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ matches.

    If an exception occurs during evaluation of Exprs but there is no matching │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ expression.

    If an exception occurs during evaluation of ExceptionBody, it is not caught.

    It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ for throw:

    try Exprs
    │ │ │  catch
    │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    The try expression can have an of section:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ case expression, except that if the matching fails, a │ │ │ try_clause run-time error occurs instead of a case_clause.

    Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ not caught.

    The try expression can also be augmented with an after section, intended to │ │ │ be used for cleanup with side effects:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  after
    │ │ │      AfterBody
    │ │ │  end

    AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ expression is the same with an after section as without.

    Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ @@ -1082,40 +1082,40 @@ │ │ │ ExpressionBody │ │ │ after │ │ │ AfterBody │ │ │ end │ │ │ │ │ │ try Exprs after AfterBody end

    Next is an example of using after. This closes the file, even in the event of │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ -exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
    │ │ │ +exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
    │ │ │      try
    │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ -        binary_to_term(Bin)
    │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ +        binary_to_term(Bin)
    │ │ │      after
    │ │ │ -        file:close(F)
    │ │ │ +        file:close(F)
    │ │ │      end.

    Next is an example of using try to emulate catch Expr:

    try Expr
    │ │ │  catch
    │ │ │      throw:Term -> Term;
    │ │ │ -    exit:Reason -> {'EXIT',Reason};
    │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │ +    exit:Reason -> {'EXIT',Reason};
    │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │  end

    Variables bound in the various parts of these expressions have different scopes. │ │ │ Variables bound just after the try keyword are:

    • bound in the of section
    • unsafe in both the catch and after sections, as well as after the whole │ │ │ construct

    Variables bound in of section are:

    • unbound in the catch section
    • unsafe in both the after section, as well as after the whole construct

    Variables bound in the catch section are unsafe in the after section, as │ │ │ well as after the whole construct.

    Variables bound in the after section are unsafe after the whole construct.

    │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │

    │ │ │ -
    (Expr)

    Parenthesized expressions are useful to override │ │ │ +

    (Expr)

    Parenthesized expressions are useful to override │ │ │ operator precedences, for example, in arithmetic │ │ │ expressions:

    1> 1 + 2 * 3.
    │ │ │  7
    │ │ │ -2> (1 + 2) * 3.
    │ │ │ +2> (1 + 2) * 3.
    │ │ │  9

    │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │

    │ │ │
    begin
    │ │ │ @@ -1127,71 +1127,71 @@
    │ │ │    
    │ │ │      
    │ │ │    
    │ │ │    Comprehensions
    │ │ │  

    │ │ │

    Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ -depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ +depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ generator or a filter.

    Bit string comprehensions construct bit strings or binaries. They have the │ │ │ -following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ +following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ -Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ +Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ is either a generator or a filter.

    Change

    Map comprehensions and map generators were introduced in Erlang/OTP 26.

    There are three kinds of generators.

    A list generator has the following syntax:

    Pattern <- ListExpr

    where ListExpr is an expression that evaluates to a list of terms.

    A bit string generator has the following syntax:

    BitstringPattern <= BitStringExpr

    where BitStringExpr is an expression that evaluates to a bit string.

    A map generator has the following syntax:

    KeyPattern := ValuePattern <- MapExpression

    where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

    A filter is an expression that evaluates to true or false.

    The variables in the generator patterns shadow previously bound variables, │ │ │ including variables bound in a previous generator pattern.

    Variables bound in a generator expression are not visible outside the │ │ │ -expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │ +expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │  * 1:5: variable 'L' is unbound

    A list comprehension returns a list, where the list elements are the result │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ filters are true.

    A bit string comprehension returns a bit string, which is created by │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ bit string generator elements for which all filters are true.

    A map comprehension returns a map, where the map elements are the result of │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ -occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

    1> [X*2 || X <- [1,2,3]].
    │ │ │ -[2,4,6]

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <= <<1,2,3>>].
    │ │ │ -[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
    │ │ │ -<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <- [1,2,3] >>.
    │ │ │ -<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <- [1,2,3]}.
    │ │ │ -#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
    │ │ │ -#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ -[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ -[{a,b},{1,2}]

    Combining elements from two list generators:

    1> [{P,Q} || P <- [a,b,c], Q <- [1,2]].
    │ │ │ -[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

    More examples are provided in │ │ │ +occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

    1> [X*2 || X <- [1,2,3]].
    │ │ │ +[2,4,6]

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <= <<1,2,3>>].
    │ │ │ +[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
    │ │ │ +<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <- [1,2,3] >>.
    │ │ │ +<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <- [1,2,3]}.
    │ │ │ +#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
    │ │ │ +#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ +[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ +[{a,b},{1,2}]

    Combining elements from two list generators:

    1> [{P,Q} || P <- [a,b,c], Q <- [1,2]].
    │ │ │ +[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

    More examples are provided in │ │ │ Programming Examples.

    When there are no generators, a comprehension returns either a term constructed │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ -[2]
    │ │ │ -2> [x || is_integer(x)].
    │ │ │ -[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ +[2]
    │ │ │ +2> [x || is_integer(x)].
    │ │ │ +[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ depends on the expression:

    • If the expression is a guard expression, │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ evaluating to false.
    • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ -comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> [E || E <- List, E rem 2].
    │ │ │ -[]
    │ │ │ -3> [E || E <- List, E rem 2 =:= 0].
    │ │ │ -[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │ +comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> [E || E <- List, E rem 2].
    │ │ │ +[]
    │ │ │ +3> [E || E <- List, E rem 2 =:= 0].
    │ │ │ +[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -3> [E || E <- List, FaultyIsEven(E)].
    │ │ │ +3> [E || E <- List, FaultyIsEven(E)].
    │ │ │  ** exception error: bad filter 1
    │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -5> [E || E <- List, IsEven(E)].
    │ │ │ +5> [E || E <- List, IsEven(E)].
    │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
    │ │ │       in operator  rem/2
    │ │ │          called as a rem 2
    │ │ │ -6> [E || E <- List, is_integer(E), IsEven(E)].
    │ │ │ -[2,4]

    │ │ │ +6> [E || E <- List, is_integer(E), IsEven(E)]. │ │ │ +[2,4]

    │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │

    │ │ │

    A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/funs.html │ │ │ @@ -117,402 +117,402 @@ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ to write several different functions that have a similar structure, but differ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ -    print_list(Stream, T);
    │ │ │ -print_list(Stream, []) ->
    │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ +    print_list(Stream, T);
    │ │ │ +print_list(Stream, []) ->
    │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │      Pid ! Msg,
    │ │ │ -    broadcast(Msg, Pids);
    │ │ │ -broadcast(_, []) ->
    │ │ │ +    broadcast(Msg, Pids);
    │ │ │ +broadcast(_, []) ->
    │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │

    │ │ │

    Funs are written with the following syntax (see │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │          ...
    │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ --export([t1/0, t2/0]).
    │ │ │ --import(lists, [map/2]).
    │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ +-export([t1/0, t2/0]).
    │ │ │ +-import(lists, [map/2]).
    │ │ │  
    │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ -   apply(F, Args);
    │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ +   apply(F, Args);
    │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ representation of a fun. The check_process_code/2 │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │

    │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.
  • The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ +    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ file:open line.

    As any variable that occurs in the head of a fun is considered a new variable, │ │ │ -it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(File) ->
    │ │ │ -                io:format(Stream,"~p~n",[File])
    │ │ │ -            end, List),
    │ │ │ -    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ +it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(File) ->
    │ │ │ +                io:format(Stream,"~p~n",[File])
    │ │ │ +            end, List),
    │ │ │ +    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ diagnostic:

    ./FileName.erl:Line: Warning: variable 'File'
    │ │ │        shadowed in 'fun'

    This indicates that the variable File, which is defined inside the fun, │ │ │ collides with the variable File, which is defined outside the fun.

    The rules for importing variables into a fun has the consequence that certain │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ -argument is Y:

    f(...) ->
    │ │ │ +argument is Y:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(X) when X == Y ->
    │ │ │ +    map(fun(X) when X == Y ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ -    ...

    instead of writing the following code:

    f(...) ->
    │ │ │ +        end, ...)
    │ │ │ +    ...

    instead of writing the following code:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(Y) ->
    │ │ │ +    map(fun(Y) ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ +        end, ...)
    │ │ │      ...

    │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │

    │ │ │

    The following examples show a dialogue with the Erlang shell. All the higher │ │ │ order functions discussed are exported from the module lists.

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ +

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ list.

    When a new fun is defined in the shell, the value of the fun is printed as │ │ │ -Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │ +Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:map(Double, [1,2,3,4,5]).
    │ │ │ -[2,4,6,8,10]

    │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ +[2,4,6,8,10]

    │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │

    │ │ │ -

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ +

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │          true  ->  true;
    │ │ │ -        false ->  any(Pred, T)
    │ │ │ +        false ->  any(Pred, T)
    │ │ │      end;
    │ │ │ -any(Pred, []) ->
    │ │ │ +any(Pred, []) ->
    │ │ │      false.

    A predicate is a function that returns true or false. any is true if │ │ │ there is a term X in the list such that P(X) is true.

    A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ -10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │ +10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:any(Big, [1,2,3,4]).
    │ │ │ +> lists:any(Big, [1,2,3,4]).
    │ │ │  false
    │ │ │ -> lists:any(Big, [1,2,3,12,5]).
    │ │ │ +> lists:any(Big, [1,2,3,12,5]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │

    │ │ │ -

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  ->  all(Pred, T);
    │ │ │ +

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  ->  all(Pred, T);
    │ │ │          false ->  false
    │ │ │      end;
    │ │ │ -all(Pred, []) ->
    │ │ │ -    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │ +all(Pred, []) ->
    │ │ │ +    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │  false
    │ │ │ -> lists:all(Big, [12,13,14,15]).
    │ │ │ +> lists:all(Big, [12,13,14,15]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │ -

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ +

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │      ok.

    The function is applied to each argument in the list. foreach returns ok. It │ │ │ -is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │ +is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │  1
    │ │ │  2
    │ │ │  3
    │ │ │  4
    │ │ │  ok

    │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │

    │ │ │ -

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ -    foldl(F, F(Hd, Accu), Tail);
    │ │ │ -foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ +

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ +    foldl(F, F(Hd, Accu), Tail);
    │ │ │ +foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ return a new accumulator, which is used the next time the function is called.

    If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ -lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ -["I","like","Erlang"]
    │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ -11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │ +lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ +["I","like","Erlang"]
    │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ +11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │  Sum = 0,
    │ │ │ -while( L != []){
    │ │ │ -    Sum += length(head(L)),
    │ │ │ -    L = tail(L)
    │ │ │ +while( L != []){
    │ │ │ +    Sum += length(head(L)),
    │ │ │ +    L = tail(L)
    │ │ │  end

    │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │

    │ │ │ -

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ -    {R,Accu1} = F(Hd, Accu0),
    │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ -    {[R|Rs], Accu2};
    │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ -then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ -(X) -> X
    │ │ │ +

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ +    {R,Accu1} = F(Hd, Accu0),
    │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ +    {[R|Rs], Accu2};
    │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ +then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ +(X) -> X
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │  > Upcase_word =
    │ │ │ -fun(X) ->
    │ │ │ -lists:map(Upcase, X)
    │ │ │ +fun(X) ->
    │ │ │ +lists:map(Upcase, X)
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Upcase_word("Erlang").
    │ │ │ +> Upcase_word("Erlang").
    │ │ │  "ERLANG"
    │ │ │ -> lists:map(Upcase_word, L).
    │ │ │ -["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ -{Upcase_word(Word), Sum + length(Word)}
    │ │ │ -end, 0, L).
    │ │ │ -{["I","LIKE","ERLANG"],11}

    │ │ │ +> lists:map(Upcase_word, L). │ │ │ +["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ +{Upcase_word(Word), Sum + length(Word)}
    │ │ │ +end, 0, L).
    │ │ │ +{["I","LIKE","ERLANG"],11}

    │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │

    │ │ │

    lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ -in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ -    case F(H) of
    │ │ │ -        true  -> [H|filter(F, T)];
    │ │ │ -        false -> filter(F, T)
    │ │ │ +in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ +    case F(H) of
    │ │ │ +        true  -> [H|filter(F, T)];
    │ │ │ +        false -> filter(F, T)
    │ │ │      end;
    │ │ │ -filter(F, []) -> [].
    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ -[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ +filter(F, []) -> [].

    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ +[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ -the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ +the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │

    │ │ │

    lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ -P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> [H|takewhile(Pred, T)];
    │ │ │ -        false -> []
    │ │ │ +P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> [H|takewhile(Pred, T)];
    │ │ │ +        false -> []
    │ │ │      end;
    │ │ │ -takewhile(Pred, []) ->
    │ │ │ -    [].
    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[200,500,45]

    │ │ │ +takewhile(Pred, []) -> │ │ │ + [].

    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[200,500,45]

    │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │

    │ │ │ -

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> dropwhile(Pred, T);
    │ │ │ -        false -> [H|T]
    │ │ │ +

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> dropwhile(Pred, T);
    │ │ │ +        false -> [H|T]
    │ │ │      end;
    │ │ │ -dropwhile(Pred, []) ->
    │ │ │ -    [].
    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[5,3,45,6]

    │ │ │ +dropwhile(Pred, []) -> │ │ │ + [].

    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[5,3,45,6]

    │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │

    │ │ │

    lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ -    splitwith(Pred, L, []).
    │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ +    splitwith(Pred, L, []).
    │ │ │  
    │ │ │ -splitwith(Pred, [H|T], L) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ -        false -> {reverse(L), [H|T]}
    │ │ │ +splitwith(Pred, [H|T], L) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ +        false -> {reverse(L), [H|T]}
    │ │ │      end;
    │ │ │ -splitwith(Pred, [], L) ->
    │ │ │ -    {reverse(L), []}.
    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ -{[200,500,45],[5,3,45,6]}

    │ │ │ +splitwith(Pred, [], L) -> │ │ │ + {reverse(L), []}.

    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ +{[200,500,45],[5,3,45,6]}

    │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │

    │ │ │

    So far, only functions that take funs as arguments have been described. More │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ following examples illustrate these type of functions.

    │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │

    │ │ │

    Adder(X) is a function that given X, returns a new function G such that │ │ │ -G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │ +G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6 = Adder(6).
    │ │ │ +> Add6 = Adder(6).
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6(10).
    │ │ │ +> Add6(10).
    │ │ │  16

    │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │

    │ │ │ -

    The idea is to write something like:

    -module(lazy).
    │ │ │ --export([ints_from/1]).
    │ │ │ -ints_from(N) ->
    │ │ │ -    fun() ->
    │ │ │ -            [N|ints_from(N+1)]
    │ │ │ -    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │ +

    The idea is to write something like:

    -module(lazy).
    │ │ │ +-export([ints_from/1]).
    │ │ │ +ints_from(N) ->
    │ │ │ +    fun() ->
    │ │ │ +            [N|ints_from(N+1)]
    │ │ │ +    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> XX().
    │ │ │ -[1|#Fun<lazy.0.29874839>]
    │ │ │ -> hd(XX()).
    │ │ │ +> XX().
    │ │ │ +[1|#Fun<lazy.0.29874839>]
    │ │ │ +> hd(XX()).
    │ │ │  1
    │ │ │ -> Y = tl(XX()).
    │ │ │ +> Y = tl(XX()).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> hd(Y()).
    │ │ │ +> hd(Y()).
    │ │ │  2

    And so on. This is an example of "lazy embedding".

    │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │

    │ │ │ -

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ +

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ {ok, Tree, Toks1}.

    • Tree is a parse tree.
    • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ structure that was correctly parsed.

    An unsuccessful parse returns fail.

    The following example illustrates a simple, functional parser that parses the │ │ │ grammar:

    (a | b) & (c | d)

    The following code defines a function pconst(X) in the module funparse, │ │ │ -which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ -    fun (T) ->
    │ │ │ +which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ +    fun (T) ->
    │ │ │         case T of
    │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
    │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
    │ │ │             _      -> fail
    │ │ │         end
    │ │ │ -    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │ +    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │  #Fun<funparse.0.22674075>
    │ │ │ -> P1([a,b,c]).
    │ │ │ -{ok,{const,a},[b,c]}
    │ │ │ -> P1([x,y,z]).
    │ │ │ +> P1([a,b,c]).
    │ │ │ +{ok,{const,a},[b,c]}
    │ │ │ +> P1([x,y,z]).
    │ │ │  fail

    Next, the two higher order functions pand and por are defined. They combine │ │ │ -primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R1, T1} ->
    │ │ │ -                case P2(T1) of
    │ │ │ -                    {ok, R2, T2} ->
    │ │ │ -                        {ok, {'and', R1, R2}};
    │ │ │ +primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R1, T1} ->
    │ │ │ +                case P2(T1) of
    │ │ │ +                    {ok, R2, T2} ->
    │ │ │ +                        {ok, {'and', R1, R2}};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end;
    │ │ │              fail ->
    │ │ │                  fail
    │ │ │          end
    │ │ │      end.

    Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

    por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ -G2:

    por(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R, T1} ->
    │ │ │ -                {ok, {'or',1,R}, T1};
    │ │ │ +G2:

    por(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R, T1} ->
    │ │ │ +                {ok, {'or',1,R}, T1};
    │ │ │              fail ->
    │ │ │ -                case P2(T) of
    │ │ │ -                    {ok, R1, T1} ->
    │ │ │ -                        {ok, {'or',2,R1}, T1};
    │ │ │ +                case P2(T) of
    │ │ │ +                    {ok, R1, T1} ->
    │ │ │ +                        {ok, {'or',2,R1}, T1};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end
    │ │ │          end
    │ │ │      end.

    The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ -code addresses this problem:

    grammar() ->
    │ │ │ -    pand(
    │ │ │ -         por(pconst(a), pconst(b)),
    │ │ │ -         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ -    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([a,d]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([b,c]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([b,d]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([a,b]).
    │ │ │ +code addresses this problem:

    grammar() ->
    │ │ │ +    pand(
    │ │ │ +         por(pconst(a), pconst(b)),
    │ │ │ +         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ +    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([a,d]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([b,c]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([b,d]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([a,b]).
    │ │ │  fail
    │ │ │ │ │ │ │ │ │

    │ │ │

    An example of a simple server written in plain Erlang is provided in │ │ │ Overview. The server can be reimplemented using │ │ │ -gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting a Gen_Server │ │ │

    │ │ │

    In the example in the previous section, gen_server is started by calling │ │ │ -ch3:start_link():

    start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ +ch3:start_link():

    start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ spawns and links to a new process, a gen_server.

    • The first argument, {local, ch3}, specifies the name. │ │ │ The gen_server is then locally registered as ch3.

      If the name is omitted, the gen_server is not registered. Instead its pid │ │ │ must be used. The name can also be given as {global, Name}, in which case │ │ │ the gen_server is registered using global:register_name/2.

    • The second argument, ch3, is the name of the callback module, which is │ │ │ the module where the callback functions are located.

      The interface functions (start_link/0, alloc/0, and free/1) are located │ │ │ in the same module as the callback functions (init/1, handle_call/3, and │ │ │ handle_cast/2). It is usually good programming practice to have the code │ │ │ corresponding to one process contained in a single module.

    • The third argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any indata and ignores the │ │ │ argument.

    • The fourth argument, [], is a list of options. See gen_server │ │ │ for the available options.

    If name registration succeeds, the new gen_server process calls the callback │ │ │ function ch3:init([]). init is expected to return {ok, State}, where │ │ │ State is the internal state of the gen_server. In this case, the state is │ │ │ -the available channels.

    init(_Args) ->
    │ │ │ -    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ +the available channels.

    init(_Args) ->
    │ │ │ +    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ gen_server has been initialized and is ready to receive requests.

    gen_server:start_link/4 must be used if the gen_server is part of │ │ │ a supervision tree, meaning that it was started by a supervisor. There │ │ │ is another function, gen_server:start/4, to start a standalone │ │ │ gen_server that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -221,32 +221,32 @@ │ │ │

    │ │ │

    The synchronous request alloc() is implemented using gen_server:call/2:

    alloc() ->
    │ │ │      gen_server:call(ch3, alloc).

    ch3 is the name of the gen_server and must agree with the name │ │ │ used to start it. alloc is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ When the request is received, the gen_server calls │ │ │ handle_call(Request, From, State), which is expected to return │ │ │ a tuple {reply,Reply,State1}. Reply is the reply that is to be sent back │ │ │ -to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ +to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ set of remaining available channels Chs2.

    Thus, the call ch3:alloc() returns the allocated channel Ch and the │ │ │ gen_server then waits for new requests, now with an updated list of │ │ │ available channels.

    │ │ │ │ │ │ │ │ │ │ │ │ Asynchronous Requests - Cast │ │ │

    │ │ │ -

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ +

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ cast, and thus free, then returns ok.

    When the request is received, the gen_server calls │ │ │ handle_cast(Request, State), which is expected to return a tuple │ │ │ -{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ +{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ +    Chs2 = free(Ch, Chs),
    │ │ │ +    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ The gen_server is now ready for new requests.

    │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

    │ │ │

    │ │ │ @@ -257,69 +257,69 @@ │ │ │

    │ │ │

    If the gen_server is part of a supervision tree, no stop function is needed. │ │ │ The gen_server is automatically terminated by its supervisor. Exactly how │ │ │ this is done is defined by a shutdown strategy │ │ │ set in the supervisor.

    If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_server must be set to trap exit signals │ │ │ in function init. When ordered to shutdown, the gen_server then calls │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │      ...,
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │      ...,
    │ │ │ -    {ok, State}.
    │ │ │ +    {ok, State}.
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(shutdown, State) ->
    │ │ │ +terminate(shutdown, State) ->
    │ │ │      %% Code for cleaning up here
    │ │ │      ...
    │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │

    │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ can be useful, for example:

    ...
    │ │ │ -export([stop/0]).
    │ │ │ +export([stop/0]).
    │ │ │  ...
    │ │ │  
    │ │ │ -stop() ->
    │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ +stop() ->
    │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │  ...
    │ │ │  
    │ │ │ -handle_cast(stop, State) ->
    │ │ │ -    {stop, normal, State};
    │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ +handle_cast(stop, State) ->
    │ │ │ +    {stop, normal, State};
    │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │      ...
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(normal, State) ->
    │ │ │ +terminate(normal, State) ->
    │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ a normal termination and State1 is a new value for the state │ │ │ of the gen_server. This causes the gen_server to call │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │

    │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │      %% Code to handle exits here.
    │ │ │      ...
    │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │      %% Code to convert state (and more) during code change.
    │ │ │      ...
    │ │ │ -    {ok, NewState}.
    │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Specifying Included Applications │ │ │

    │ │ │

    Which applications to include is defined by the included_applications key in │ │ │ -the .app file:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {prim_app_cb,[]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.

    │ │ │ +the .app file:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {prim_app_cb,[]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.

    │ │ │ │ │ │ │ │ │ │ │ │ Synchronizing Processes during Startup │ │ │

    │ │ │

    The supervisor tree of an included application is started as part of the │ │ │ supervisor tree of the including application. If there is a need for │ │ │ synchronization between processes in the including and included applications, │ │ │ this can be achieved by using start phases.

    Start phases are defined by the start_phases key in the .app file as a list │ │ │ of tuples {Phase,PhaseArgs}, where Phase is an atom and PhaseArgs is a │ │ │ term.

    The value of the mod key of the including application must be set to │ │ │ {application_starter,[Module,StartArgs]}, where Module as usual is the │ │ │ application callback module. StartArgs is a term provided as argument to the │ │ │ -callback function Module:start/2:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.
    │ │ │ +callback function Module:start/2:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.
    │ │ │  
    │ │ │ -{application, incl_app,
    │ │ │ - [{description, "Included application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [incl_app_cb, incl_app_sup, incl_app_server]},
    │ │ │ -  {registered, []},
    │ │ │ -  {start_phases, [{go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {incl_app_cb,[]}}
    │ │ │ - ]}.

    When starting a primary application with included applications, the primary │ │ │ +{application, incl_app, │ │ │ + [{description, "Included application"}, │ │ │ + {vsn, "1"}, │ │ │ + {modules, [incl_app_cb, incl_app_sup, incl_app_server]}, │ │ │ + {registered, []}, │ │ │ + {start_phases, [{go,[]}]}, │ │ │ + {applications, [kernel, stdlib, sasl]}, │ │ │ + {mod, {incl_app_cb,[]}} │ │ │ + ]}.

    When starting a primary application with included applications, the primary │ │ │ application is started the normal way, that is:

    • The application controller creates an application master for the application
    • The application master calls Module:start(normal, StartArgs) to start the │ │ │ top supervisor.

    Then, for the primary application and each included application in top-down, │ │ │ left-to-right order, the application master calls │ │ │ Module:start_phase(Phase, Type, PhaseArgs) for each phase defined for the │ │ │ primary application, in that order. If a phase is not defined for an included │ │ │ application, the function is not called for this phase and application.

    The following requirements apply to the .app file for an included application:

    • The {mod, {Module,StartArgs}} option must be included. This option is used │ │ │ to find the callback module Module of the application. StartArgs is │ │ │ ignored, as Module:start/2 is called only for the primary application.
    • If the included application itself contains included applications, instead the │ │ │ {mod, {application_starter, [Module,StartArgs]}} option must be included.
    • The {start_phases, [{Phase,PhaseArgs}]} option must be included, and the set │ │ │ of specified phases must be a subset of the set of phases specified for the │ │ │ primary application.

    When starting prim_app as defined above, the application controller calls the │ │ │ following callback functions before application:start(prim_app) returns a │ │ │ -value:

    application:start(prim_app)
    │ │ │ - => prim_app_cb:start(normal, [])
    │ │ │ - => prim_app_cb:start_phase(init, normal, [])
    │ │ │ - => prim_app_cb:start_phase(go, normal, [])
    │ │ │ - => incl_app_cb:start_phase(go, normal, [])
    │ │ │ +value:

    application:start(prim_app)
    │ │ │ + => prim_app_cb:start(normal, [])
    │ │ │ + => prim_app_cb:start_phase(init, normal, [])
    │ │ │ + => prim_app_cb:start_phase(go, normal, [])
    │ │ │ + => incl_app_cb:start_phase(go, normal, [])
    │ │ │  ok
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Frequently Asked Questions │ │ │

    │ │ │
    • Q: So, now I can build Erlang using GCC on Windows?

      A: No, unfortunately not. You'll need Microsoft's Visual C++ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/list_comprehensions.html │ │ │ @@ -117,33 +117,33 @@ │ │ │ │ │ │

      │ │ │ │ │ │ │ │ │ │ │ │ Simple Examples │ │ │

      │ │ │ -

      This section starts with a simple example, showing a generator and a filter:

      > [X || X <- [1,2,a,3,4,b,5,6], X > 3].
      │ │ │ -[a,4,b,5,6]

      This is read as follows: The list of X such that X is taken from the list │ │ │ +

      This section starts with a simple example, showing a generator and a filter:

      > [X || X <- [1,2,a,3,4,b,5,6], X > 3].
      │ │ │ +[a,4,b,5,6]

      This is read as follows: The list of X such that X is taken from the list │ │ │ [1,2,a,...] and X is greater than 3.

      The notation X <- [1,2,a,...] is a generator and the expression X > 3 is a │ │ │ filter.

      An additional filter, is_integer(X), can be added to │ │ │ -restrict the result to integers:

      > [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
      │ │ │ -[4,5,6]

      Generators can be combined. For example, the Cartesian product of two lists can │ │ │ -be written as follows:

      > [{X, Y} || X <- [1,2,3], Y <- [a,b]].
      │ │ │ -[{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]

      │ │ │ +restrict the result to integers:

      > [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
      │ │ │ +[4,5,6]

      Generators can be combined. For example, the Cartesian product of two lists can │ │ │ +be written as follows:

      > [{X, Y} || X <- [1,2,3], Y <- [a,b]].
      │ │ │ +[{1,a},{1,b},{2,a},{2,b},{3,a},{3,b}]

      │ │ │ │ │ │ │ │ │ │ │ │ Quick Sort │ │ │

      │ │ │ -

      The well-known quick sort routine can be written as follows:

      sort([]) -> [];
      │ │ │ -sort([_] = L) -> L;
      │ │ │ -sort([Pivot|T]) ->
      │ │ │ -    sort([ X || X <- T, X < Pivot]) ++
      │ │ │ -    [Pivot] ++
      │ │ │ -    sort([ X || X <- T, X >= Pivot]).

      The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ +

      The well-known quick sort routine can be written as follows:

      sort([]) -> [];
      │ │ │ +sort([_] = L) -> L;
      │ │ │ +sort([Pivot|T]) ->
      │ │ │ +    sort([ X || X <- T, X < Pivot]) ++
      │ │ │ +    [Pivot] ++
      │ │ │ +    sort([ X || X <- T, X >= Pivot]).

      The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ that are less than Pivot.

      [X || X <- T, X >= Pivot] is the list of all elements in T that are greater │ │ │ than or equal to Pivot.

      With the algorithm above, a list is sorted as follows:

      • A list with zero or one element is trivially sorted.
      • For lists with more than one element:
        1. The first element in the list is isolated as the pivot element.
        2. The remaining list is partitioned into two sublists, such that:
        • The first sublist contains all elements that are smaller than the pivot │ │ │ element.
        • The second sublist contains all elements that are greater than or equal to │ │ │ the pivot element.
        1. The sublists are recursively sorted by the same algorithm and the results │ │ │ are combined, resulting in a list consisting of:
        • All elements from the first sublist, that is all elements smaller than the │ │ │ pivot element, in sorted order.
        • The pivot element.
        • All elements from the second sublist, that is all elements greater than or │ │ │ equal to the pivot element, in sorted order.

      Note

      While the sorting algorithm as shown above serves as a nice example to │ │ │ @@ -151,93 +151,93 @@ │ │ │ lists module contains sorting functions that are implemented in a more │ │ │ efficient way.

      │ │ │ │ │ │ │ │ │ │ │ │ Permutations │ │ │

      │ │ │ -

      The following example generates all permutations of the elements in a list:

      perms([]) -> [[]];
      │ │ │ -perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

      This takes H from L in all possible ways. The result is the set of all lists │ │ │ +

      The following example generates all permutations of the elements in a list:

      perms([]) -> [[]];
      │ │ │ +perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

      This takes H from L in all possible ways. The result is the set of all lists │ │ │ [H|T], where T is the set of all possible permutations of L, with H │ │ │ -removed:

      > perms([b,u,g]).
      │ │ │ -[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

      │ │ │ +removed:

      > perms([b,u,g]).
      │ │ │ +[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

      │ │ │ │ │ │ │ │ │ │ │ │ Pythagorean Triplets │ │ │

      │ │ │

      Pythagorean triplets are sets of integers {A,B,C} such that │ │ │ A**2 + B**2 = C**2.

      The function pyth(N) generates a list of all integers {A,B,C} such that │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ -N:

      pyth(N) ->
      │ │ │ -    [ {A,B,C} ||
      │ │ │ -        A <- lists:seq(1,N),
      │ │ │ -        B <- lists:seq(1,N),
      │ │ │ -        C <- lists:seq(1,N),
      │ │ │ +N:

      pyth(N) ->
      │ │ │ +    [ {A,B,C} ||
      │ │ │ +        A <- lists:seq(1,N),
      │ │ │ +        B <- lists:seq(1,N),
      │ │ │ +        C <- lists:seq(1,N),
      │ │ │          A+B+C =< N,
      │ │ │          A*A+B*B == C*C
      │ │ │ -    ].
      > pyth(3).
      │ │ │ -[].
      │ │ │ -> pyth(11).
      │ │ │ -[].
      │ │ │ -> pyth(12).
      │ │ │ -[{3,4,5},{4,3,5}]
      │ │ │ -> pyth(50).
      │ │ │ -[{3,4,5},
      │ │ │ - {4,3,5},
      │ │ │ - {5,12,13},
      │ │ │ - {6,8,10},
      │ │ │ - {8,6,10},
      │ │ │ - {8,15,17},
      │ │ │ - {9,12,15},
      │ │ │ - {12,5,13},
      │ │ │ - {12,9,15},
      │ │ │ - {12,16,20},
      │ │ │ - {15,8,17},
      │ │ │ - {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ -   [{A,B,C} ||
      │ │ │ -       A <- lists:seq(1,N-2),
      │ │ │ -       B <- lists:seq(A+1,N-1),
      │ │ │ -       C <- lists:seq(B+1,N),
      │ │ │ +    ].
      > pyth(3).
      │ │ │ +[].
      │ │ │ +> pyth(11).
      │ │ │ +[].
      │ │ │ +> pyth(12).
      │ │ │ +[{3,4,5},{4,3,5}]
      │ │ │ +> pyth(50).
      │ │ │ +[{3,4,5},
      │ │ │ + {4,3,5},
      │ │ │ + {5,12,13},
      │ │ │ + {6,8,10},
      │ │ │ + {8,6,10},
      │ │ │ + {8,15,17},
      │ │ │ + {9,12,15},
      │ │ │ + {12,5,13},
      │ │ │ + {12,9,15},
      │ │ │ + {12,16,20},
      │ │ │ + {15,8,17},
      │ │ │ + {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ +   [{A,B,C} ||
      │ │ │ +       A <- lists:seq(1,N-2),
      │ │ │ +       B <- lists:seq(A+1,N-1),
      │ │ │ +       C <- lists:seq(B+1,N),
      │ │ │         A+B+C =< N,
      │ │ │ -       A*A+B*B == C*C ].

      │ │ │ + A*A+B*B == C*C ].

      │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │

      │ │ │

      As an example, list comprehensions can be used to simplify some of the functions │ │ │ -in lists.erl:

      append(L)   ->  [X || L1 <- L, X <- L1].
      │ │ │ -map(Fun, L) -> [Fun(X) || X <- L].
      │ │ │ -filter(Pred, L) -> [X || X <- L, Pred(X)].

      │ │ │ +in lists.erl:

      append(L)   ->  [X || L1 <- L, X <- L1].
      │ │ │ +map(Fun, L) -> [Fun(X) || X <- L].
      │ │ │ +filter(Pred, L) -> [X || X <- L, Pred(X)].

      │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │

      │ │ │

      The scope rules for variables that occur in list comprehensions are as follows:

      • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ variables.
      • Any variables that are defined before the list comprehension, and that are │ │ │ used in filters, have the values they had before the list comprehension.
      • Variables cannot be exported from a list comprehension.

      As an example of these rules, suppose you want to write the function select, │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ tuples from L, where the first item is X.

      Compiling this gives the following diagnostic:

      ./FileName.erl:Line: Warning: variable 'X' shadowed in generate

      This diagnostic warns that the variable X in the pattern is not the same as │ │ │ -the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ -written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ -the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ -same name bound in a previous generator pattern. For example:

      > [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
      │ │ │ -[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ +the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ +written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ +the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ +same name bound in a previous generator pattern. For example:

      > [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
      │ │ │ +[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ -cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │ +cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ -    ...

      Instead, write as follows:

      f(...) ->
      │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ +    ...

      Instead, write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │      ...
      │ │ │ │ │ │ │ │ │
      │ │ │
      │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │ │ │

      Lists can only be built starting from the end and attaching list elements at the │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ is a copy of the elements in List1, followed by List2:

      List1 ++ List2

      Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ -clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ -    [H|append(T, Tail)];
      │ │ │ -append([], Tail) ->
      │ │ │ +clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ +    [H|append(T, Tail)];
      │ │ │ +append([], Tail) ->
      │ │ │      Tail.

      When recursing and building a list, it is important to ensure that you attach │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ -list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ -    bad_fib(N, 0, 1, []).
      │ │ │ +list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ +    bad_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │      Fibs;
      │ │ │ -bad_fib(N, Current, Next, Fibs) ->
      │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ that is one element longer than the new previous list.

      To avoid copying the result in each iteration, build the list in reverse order │ │ │ -and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ -    tail_recursive_fib(N, 0, 1, []).
      │ │ │ +and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ +    tail_recursive_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
      │ │ │ -    lists:reverse(Fibs);
      │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
      │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ + lists:reverse(Fibs); │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │

      │ │ │ -

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ -'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ -will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ +

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ +'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ +will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │  ok.

      or in this code:

      case Var of
      │ │ │      ... ->
      │ │ │ -        [io:put_chars(E) || E <- List];
      │ │ │ +        [io:put_chars(E) || E <- List];
      │ │ │      ... ->
      │ │ │  end,
      │ │ │ -some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ +some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ -will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    Expr(E),
      │ │ │ -    'lc^0'(Tail, Expr);
      │ │ │ -'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ +will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    Expr(E),
      │ │ │ +    'lc^0'(Tail, Expr);
      │ │ │ +'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │  ok.

      │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │

      │ │ │

      lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ but not its right argument).

      In the following situations it is unnecessary to call lists:flatten/1:

      • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ to flatten the list before sending it to the port.
      • When calling BIFs that accept deep lists, such as │ │ │ list_to_binary/1 or │ │ │ iolist_to_binary/1.
      • When you know that your list is only one level deep. Use lists:append/1 │ │ │ -instead.

      Examples:

      DO

      port_command(Port, DeepList)

      DO NOT

      port_command(Port, lists:flatten(DeepList))

      A common way to send a zero-terminated string to a port is the following:

      DO NOT

      TerminatedStr = String ++ [0],
      │ │ │ -port_command(Port, TerminatedStr)

      Instead:

      DO

      TerminatedStr = [String, 0],
      │ │ │ -port_command(Port, TerminatedStr)

      DO

      1> lists:append([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      DO NOT

      1> lists:flatten([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      │ │ │ +instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ +port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ +port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │

    │ │ │

    There are two basic ways to write a function that traverses a list and │ │ │ produces a new list.

    The first way is writing a body-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_body([H|T]) ->
    │ │ │ -    [H + 42 | add_42_body(T)];
    │ │ │ -add_42_body([]) ->
    │ │ │ -    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_tail(List) ->
    │ │ │ -    add_42_tail(List, []).
    │ │ │ -
    │ │ │ -add_42_tail([H|T], Acc) ->
    │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ -add_42_tail([], Acc) ->
    │ │ │ -    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ +add_42_body([H|T]) -> │ │ │ + [H + 42 | add_42_body(T)]; │ │ │ +add_42_body([]) -> │ │ │ + [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ +add_42_tail(List) ->
    │ │ │ +    add_42_tail(List, []).
    │ │ │ +
    │ │ │ +add_42_tail([H|T], Acc) ->
    │ │ │ +    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ +add_42_tail([], Acc) ->
    │ │ │ +    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ much difference in performance between a body-recursive list function and │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ measure before rewriting your code.

    For a thorough discussion about tail and body recursion, see │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

    Note

    This section is about list functions that construct lists. A tail-recursive │ │ │ function that does not construct a list runs in constant space, while the │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ length of the list.

    For example, a function that sums a list of integers, is not to be written as │ │ │ -follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ -recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │ +follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ +recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │  
    │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
    │ │ │ -sum([], Sum)    -> Sum.
    │ │ │ +
    sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ +sum([], Sum) -> Sum.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ File Inclusion │ │ │

    │ │ │ -

    A file can be included as follows:

    -include(File).
    │ │ │ --include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ +

    A file can be included as follows:

    -include(File).
    │ │ │ +-include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ as is, at the position of the directive.

    Include files are typically used for record and macro definitions that are │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ .hrl for include files.

    File can start with a path component $VAR, for some string VAR. If that is │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ $VAR is left as is.

    If the filename File is absolute (possibly after variable substitution), the │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ searched for in the following directories, and in this order:

    1. The current working directory
    2. The directory where the module is being compiled
    3. The directories given by the include option

    For details, see erlc in ERTS and │ │ │ -compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ --include("incdir/my_records.hrl").
    │ │ │ --include("/home/user/proj/my_records.hrl").
    │ │ │ --include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ +compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ +-include("incdir/my_records.hrl").
    │ │ │ +-include("/home/user/proj/my_records.hrl").
    │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ -assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ +assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ the file file.hrl.

    │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │

    │ │ │ -

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ --define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ +

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ +-define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ macro.

    If a macro is used in several modules, it is recommended that the macro │ │ │ definition is placed in an include file.

    A macro is used as follows:

    ?Const
    │ │ │  ?Func(Arg1,...,ArgN)

    Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ -Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │ +Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │  ...
    │ │ │ -call(Request) ->
    │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ -    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ +call(Request) -> │ │ │ + server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ +    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ -corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │ +corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │  ...
    │ │ │ -bar(X) ->
    │ │ │ -    ?MACRO1(a, b),
    │ │ │ -    ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ -    {a,a,b,b},
    │ │ │ -    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ +bar(X) -> │ │ │ + ?MACRO1(a, b), │ │ │ + ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ +    {a,a,b,b},
    │ │ │ +    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ definition is a valid Erlang syntactic form.

    To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ after preprocessing and parse transforms, in the file File.P.

    │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ @@ -185,29 +185,29 @@ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │

    │ │ │

    It is possible to overload macros, except for predefined macros. An overloaded │ │ │ macro has more than one definition, each with a different number of arguments.

    Change

    Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

    A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ in an error message if there is at least one definition of Func with │ │ │ -arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ --define(F1(A), A).
    │ │ │ --define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │ +arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ +-define(F1(A), A).
    │ │ │ +-define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │      ?F0. % No, an empty list of arguments expected.
    │ │ │  
    │ │ │ -f1(A) ->
    │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ -    ?C().

    is expanded to

    f() ->
    │ │ │ -    m:f().

    │ │ │ +f1(A) -> │ │ │ + ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ +    ?C().

    is expanded to

    f() ->
    │ │ │ +    m:f().

    │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │

    │ │ │ -

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ +

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │

    │ │ │

    The following macro directives support conditional compilation:

    • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ defined.

    • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ @@ -219,43 +219,43 @@ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ are evaluated instead.

    • -endif. - Specifies the end of a series of control flow directives.

    Note

    Macro directives cannot be used inside functions.

    Syntactically, the Condition in if and elif must be a │ │ │ guard expression. Other constructs (such as │ │ │ a case expression) result in a compilation error.

    As opposed to the standard guard expressions, an expression in an if and │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │  ...
    │ │ │  
    │ │ │ --ifdef(debug).
    │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │ +-ifdef(debug).
    │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │  -else.
    │ │ │ --define(LOG(X), true).
    │ │ │ +-define(LOG(X), true).
    │ │ │  -endif.
    │ │ │  
    │ │ │  ...

    When trace output is desired, debug is to be defined when the module m is │ │ │ compiled:

    % erlc -Ddebug m.erl
    │ │ │  
    │ │ │  or
    │ │ │  
    │ │ │ -1> c(m, {d, debug}).
    │ │ │ -{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ -with some simple trace output.

    Example:

    -module(m)
    │ │ │ +1> c(m, {d, debug}).
    │ │ │ +{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ +with some simple trace output.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 25).
    │ │ │ +-if(?OTP_RELEASE >= 25).
    │ │ │  %% Code that will work in OTP 25 or higher
    │ │ │ --elif(?OTP_RELEASE >= 26).
    │ │ │ +-elif(?OTP_RELEASE >= 26).
    │ │ │  %% Code that will work in OTP 26 or higher
    │ │ │  -else.
    │ │ │  %% Code that will work in OTP 24 or lower.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ -release.

    Example:

    -module(m)
    │ │ │ +release.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │  %% Debugging code that requires OTP 26 or later.
    │ │ │  -else.
    │ │ │  %% Non-debug code that works in any release.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ code only for OTP 26 or later.

    │ │ │ │ │ │ @@ -270,44 +270,44 @@ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │

    │ │ │ -

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifdef(VERSION).
    │ │ │ -version() -> ?VERSION.
    │ │ │ +-ifdef(VERSION).
    │ │ │ +version() -> ?VERSION.
    │ │ │  -else.
    │ │ │ --error("Macro VERSION must be defined.").
    │ │ │ -version() -> "".
    │ │ │ +-error("Macro VERSION must be defined.").
    │ │ │ +version() -> "".
    │ │ │  -endif.

    The error message will look like this:

    % erlc t.erl
    │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifndef(VERSION).
    │ │ │ --warning("Macro VERSION not defined -- using default version.").
    │ │ │ --define(VERSION, "0").
    │ │ │ +-ifndef(VERSION).
    │ │ │ +-warning("Macro VERSION not defined -- using default version.").
    │ │ │ +-define(VERSION, "0").
    │ │ │  -endif.
    │ │ │ -version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │ +version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

    Change

    The -error() and -warning() directives were added in Erlang/OTP 19.

    │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │

    │ │ │

    The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ -stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │ +stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │  
    │ │ │ -?TESTCALL(myfunction(1,2)),
    │ │ │ -?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │ +
    ?TESTCALL(myfunction(1,2)), │ │ │ +?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │
  • maps:get/3 function. If there are default │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ values in one go.

  • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ -efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)
  • │ │ │ +efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │

    │ │ │

    Using a map as a dictionary implies the following usage pattern:

    • Keys are usually variables not known at compile-time.
    • There can be any number of elements in the map.
    • Usually, no more than one element is looked up or updated at once.

    Given that usage pattern, the difference in performance between using the map │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ @@ -167,18 +167,18 @@ │ │ │ choice.

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │

    │ │ │

    Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ -Examples:

    1> sets:new([{version,2}]).
    │ │ │ -#{}
    │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ -#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ +Examples:

    1> sets:new([{version,2}]).
    │ │ │ +#{}
    │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ +#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ few possible exceptions:

    • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ be a better choice than sets.
    • If the intersection operation is frequently used and operations that operate │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ gb_sets can potentially be a better choice than sets.
    • If the elements of the set are integers in a fairly compact range, the set can │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ @@ -203,18 +203,18 @@ │ │ │ for the runtime system).

    • N - The number of elements in the map.

    • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ sorted.

    • Value1 - The value corresponding to the first key in the key tuple.

    • ValueN - The value corresponding to the last key in the key tuple.

    As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ represented:

    01234
    FLATMAP2{a,z}foobar

    Table: #{a => foo, z => bar}

    Let us update the map: M#{q => baz}. The map now looks like this:

    012345
    FLATMAP3{a,q,z}foobazbar

    Table: #{a => foo, q => baz, z => bar}

    Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ like this:

    012345
    FLATMAP3{a,q,z}foobazbird

    Table: #{a => foo, q => baz, z => bird}

    When the value for an existing key is updated, the key tuple is not updated, │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ -For example:

    new() ->
    │ │ │ -    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ +For example:

    new() ->
    │ │ │ +    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ -new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ +new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ left to right.)

    The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ See Memory for more information about memory sizes.

    │ │ │ │ │ │ @@ -241,21 +241,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │

    │ │ │

    Using the map syntax is usually slightly more efficient than using the │ │ │ corresponding function in the maps module.

    The gain in efficiency for the map syntax is more noticeable for the following │ │ │ -operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ -Map3 = maps:update(y, Y, Map2),
    │ │ │ -Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ -right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ -left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ -Map3 = Map2#{Key2 := Y},
    │ │ │ -Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ +operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ +Map3 = maps:update(y, Y, Map2),
    │ │ │ +Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ +right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ +left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ +Map3 = Map2#{Key2 := Y},
    │ │ │ +Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ efficient than using the => operator for a small map.

    │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │

    │ │ │

    Here follows some notes about most of the functions in the maps module. For │ │ │ @@ -306,23 +306,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │

    As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ code similar to the following:

    Result = case Map of
    │ │ │ -             #{Key := Value} -> Value;
    │ │ │ -             #{} -> Default
    │ │ │ +             #{Key := Value} -> Value;
    │ │ │ +             #{} -> Default
    │ │ │           end

    This is reasonably efficient, but if a small map is used as an alternative to │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ not storing default values in the map.

    If default values are nevertheless required, instead of calling maps:get/3 │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ -map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ +map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ default values.

    Change

    Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ longer be traced.

    │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/modules.html │ │ │ @@ -118,20 +118,20 @@ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │

    │ │ │

    Erlang code is divided into modules. A module consists of a sequence of │ │ │ -attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ --export([fact/1]).   % module attribute
    │ │ │ +attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ +-export([fact/1]).   % module attribute
    │ │ │  
    │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ -    N * fact(N-1);   %  |
    │ │ │ -fact(0) ->           %  |
    │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ +    N * fact(N-1);   %  |
    │ │ │ +fact(0) ->           %  |
    │ │ │      1.               % end of function declaration

    For a description of function declarations, see │ │ │ Function Declaration Syntax.

    │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │

    │ │ │ @@ -176,71 +176,71 @@ │ │ │ meaning.

    │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │

    │ │ │

    It is possible to specify that the module is the callback module for a │ │ │ -behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ +behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

    • gen_server
    • gen_statem
    • gen_event
    • supervisor

    The spelling behavior is also accepted.

    The callback functions of the module can be specified either directly by the │ │ │ -exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ +exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ produce documentation or find discrepancies.

    Read more about behaviours and callback modules in │ │ │ OTP Design Principles.

    │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │

    │ │ │ -

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ +

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ declarations. Read more in Records.

    │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │

    │ │ │

    The same syntax as for module attributes is used by the preprocessor, which │ │ │ -supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ --define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ +supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ +-define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │

    │ │ │

    The same syntax as for module attributes is used for changing the pre-defined │ │ │ -macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ +macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ source program is generated by another tool. It also indicates the │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ which the source program is produced.

    │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │

    │ │ │

    A similar syntax as for module attributes is used for specifying types and │ │ │ -function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ --spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ +function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ +-spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ EEP8 - Types and function specifications, │ │ │ which is not to be further updated.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │

    │ │ │

    The module attribute -doc(Documentation) is used to provide user documentation │ │ │ -for a function/type/callback:

    -doc("Example documentation").
    │ │ │ -example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ +for a function/type/callback:

    -doc("Example documentation").
    │ │ │ +example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ Documentation are:

    • literal string or │ │ │ utf-8 encoded binary string - The string │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ triple quoted strings and │ │ │ sigils that translate to literal strings can be used. │ │ │ -The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ --doc(<<"Example \"docs\""/utf8>>).
      │ │ │ +The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
      │ │ │  -doc ~S/Example "docs"/.
      │ │ │  -doc """
      │ │ │     Example "docs"
      │ │ │     """
      │ │ │  -doc ~B|Example "docs"|.

      For clarity it is recommended to use either normal "strings" or triple │ │ │ quoted strings for documentation attributes.

    • {file, file:name/0 } - Read the contents of filename and use │ │ │ that as the documentation string.

    • false - Set the current entity as hidden, that is, it should not be │ │ │ @@ -253,15 +253,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │

    │ │ │

    While not a module attribute, but rather a directive (since it might affect │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ -features.

    The syntax is similar to that of an attribute, but has two arguments:

    -feature(FeatureName, enable | disable).

    Note that the feature directive can only appear │ │ │ +features.

    The syntax is similar to that of an attribute, but has two arguments:

    -feature(FeatureName, enable | disable).

    Note that the feature directive can only appear │ │ │ in a prefix of the module.

    │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │

    │ │ │

    Comments can be placed anywhere in a module except within strings and │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/nif.html │ │ │ @@ -133,26 +133,26 @@ │ │ │ Erlang Program │ │ │ │ │ │

    Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ two reasons:

    • The NIF library must be explicitly loaded by Erlang code in the same module.
    • All NIFs of a module must have an Erlang implementation as well.

    Normally these are minimal stub implementations that throw an exception. But │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ native implementations on some architectures.

    NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ -passed on to the library and used for initialization:

    -module(complex6).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ --nifs([foo/1, bar/1]).
    │ │ │ --on_load(init/0).
    │ │ │ -
    │ │ │ -init() ->
    │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
    │ │ │ -
    │ │ │ -foo(_X) ->
    │ │ │ -    erlang:nif_error(nif_library_not_loaded).
    │ │ │ -bar(_Y) ->
    │ │ │ -    erlang:nif_error(nif_library_not_loaded).

    Here, the directive on_load is used to get function init to be automatically │ │ │ +passed on to the library and used for initialization:

    -module(complex6).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +-nifs([foo/1, bar/1]).
    │ │ │ +-on_load(init/0).
    │ │ │ +
    │ │ │ +init() ->
    │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
    │ │ │ +
    │ │ │ +foo(_X) ->
    │ │ │ +    erlang:nif_error(nif_library_not_loaded).
    │ │ │ +bar(_Y) ->
    │ │ │ +    erlang:nif_error(nif_library_not_loaded).

    Here, the directive on_load is used to get function init to be automatically │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ unloaded and calls to functions within it, fail.

    Loading the NIF library overrides the stub implementations and cause calls to │ │ │ foo and bar to be dispatched to the NIF implementations instead.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -209,23 +209,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

    │ │ │

    Step 1. Compile the C code:

    unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
    │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

    Step 2: Start Erlang and compile the Erlang code:

    > erl
    │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
    │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
    │ │ │  
    │ │ │ -Eshell V5.7.5  (abort with ^G)
    │ │ │ -1> c(complex6).
    │ │ │ -{ok,complex6}

    Step 3: Run the example:

    3> complex6:foo(3).
    │ │ │ +Eshell V5.7.5  (abort with ^G)
    │ │ │ +1> c(complex6).
    │ │ │ +{ok,complex6}

    Step 3: Run the example:

    3> complex6:foo(3).
    │ │ │  4
    │ │ │ -4> complex6:bar(5).
    │ │ │ +4> complex6:bar(5).
    │ │ │  10
    │ │ │ -5> complex6:foo("not an integer").
    │ │ │ +5> complex6:foo("not an integer").
    │ │ │  ** exception error: bad argument
    │ │ │       in function  complex6:foo/1
    │ │ │          called as comlpex6:foo("not an integer")
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/opaques.html │ │ │ @@ -122,24 +122,24 @@ │ │ │ Opaque Type Aliases │ │ │ │ │ │

    The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ the rest is up to convention.

    This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ the example of the sets:set() data type. This type was │ │ │ -defined in the sets module like this:

    -opaque set(Element) :: #set{segs :: segs(Element)}.

    OTP 24 changed the definition to the following in │ │ │ -this commit.

    -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

    And this change was safer and more backwards-compatible than if the type had │ │ │ +defined in the sets module like this:

    -opaque set(Element) :: #set{segs :: segs(Element)}.

    OTP 24 changed the definition to the following in │ │ │ +this commit.

    -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

    And this change was safer and more backwards-compatible than if the type had │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ on the definition of the type: no other modules should rely on the definition.

    This means that code that pattern-matched on set as a record/tuple technically │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ -error:

    case sets:new() of
    │ │ │ -    Set when is_tuple(Set) ->
    │ │ │ -        io:format("ok")
    │ │ │ +error:

    case sets:new() of
    │ │ │ +    Set when is_tuple(Set) ->
    │ │ │ +        io:format("ok")
    │ │ │  end.

    When working with an opaque defined in another module, here are some │ │ │ recommendations:

    • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ that reveal the type, such as tuple_size/1 .
    • Instead, use functions provided by the module for working with the type. For │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ sets:is_element/2, and so on.
    • sets:set(a) is a subtype of sets:set(a | b) and not the │ │ │ other way around. Generally, you can rely on the property that the_opaque(T) │ │ │ is a subtype of the_opaque(U) when T is a subtype of U.

    When defining your own opaques, here are some recommendations:

    • Since consumers are expected to not rely on the definition of the opaque type, │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/otp-patch-apply.html │ │ │ @@ -201,15 +201,15 @@ │ │ │ │ │ │ Sanity check │ │ │ │ │ │

      The application dependencies can be checked using the Erlang shell. │ │ │ Application dependencies are verified among installed applications by │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ By calling system_information:sanity_check() one can validate │ │ │ -dependencies among applications actually loaded.

      1> system_information:sanity_check().
      │ │ │ +dependencies among applications actually loaded.

      1> system_information:sanity_check().
      │ │ │  ok

      Please take a look at the reference of sanity_check() for more │ │ │ information.

      │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/patterns.html │ │ │ @@ -128,18 +128,18 @@ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ fails, an exception is raised.

    Examples:

    1> X.
    │ │ │  ** 1:1: variable 'X' is unbound **
    │ │ │  2> X = 2.
    │ │ │  2
    │ │ │  3> X + 1.
    │ │ │  3
    │ │ │ -4> {X, Y} = {1, 2}.
    │ │ │ +4> {X, Y} = {1, 2}.
    │ │ │  ** exception error: no match of right hand side value {1,2}
    │ │ │ -5> {X, Y} = {2, 3}.
    │ │ │ -{2,3}
    │ │ │ +5> {X, Y} = {2, 3}.
    │ │ │ +{2,3}
    │ │ │  6> Y.
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/prog_ex_records.html │ │ │ @@ -122,105 +122,105 @@ │ │ │ Records and Tuples │ │ │ │ │ │

    The main advantage of using records rather than tuples is that fields in a │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ with the tuple {Name, Address, Phone}.

    To write functions that manipulate this data, remember the following:

    • The Name field is the first element of the tuple.
    • The Address field is the second element.
    • The Phone field is the third element.

    For example, to extract data from a variable P that contains such a tuple, you │ │ │ can write the following code and then use pattern matching to extract the │ │ │ -relevant fields:

    Name = element(1, P),
    │ │ │ -Address = element(2, P),
    │ │ │ +relevant fields:

    Name = element(1, P),
    │ │ │ +Address = element(2, P),
    │ │ │  ...

    Such code is difficult to read and understand, and errors occur if the numbering │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ person tuple must be checked and possibly modified.

    Records allow references to the fields by name, instead of by position. In the │ │ │ -following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ +following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ is a variable whose value is a person record, the following code access the │ │ │ name and address fields of the records:

    Name = P#person.name,
    │ │ │  Address = P#person.address,
    │ │ │ -...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ +...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │

    │ │ │

    This following definition of a person is used in several examples in this │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ -field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ -the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │ +field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ +the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │  person

    This is because record definitions are only available at compile time, not at │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │

    │ │ │ -

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ -special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ +

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ +special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ wildcard in ets:match/2.)

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │

    │ │ │ -

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │ +

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │  > P#person.name.
    │ │ │  "Joe"

    │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │

    │ │ │ -

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ -> P2 = P1#person{name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ +

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ +> P2 = P1#person{name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │

    │ │ │

    The following example shows that the guard succeeds if P is record of type │ │ │ -person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ -foo(_) -> not_a_person.

    │ │ │ +person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ +foo(_) -> not_a_person.

    │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │

    │ │ │

    Matching can be used in combination with records, as shown in the following │ │ │ -example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ -> #person{name = Name} = P3, Name.
    │ │ │ +example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ +> #person{name = Name} = P3, Name.
    │ │ │  "Joe"

    The following function takes a list of person records and searches for the │ │ │ -phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ -    {found,  Phone};
    │ │ │ -find_phone([_| T], Name) ->
    │ │ │ -    find_phone(T, Name);
    │ │ │ -find_phone([], Name) ->
    │ │ │ +phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ +    {found,  Phone};
    │ │ │ +find_phone([_| T], Name) ->
    │ │ │ +    find_phone(T, Name);
    │ │ │ +find_phone([], Name) ->
    │ │ │      not_found.

    The fields referred to in the pattern can be given in any order.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │

    The value of a field in a record can be an instance of a record. Retrieval of │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ -example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ --record(person, {name = #name{}, phone}).
    │ │ │ +example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ +-record(person, {name = #name{}, phone}).
    │ │ │  
    │ │ │ -demo() ->
    │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
    │ │ │ -  First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ +demo() -> │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ + First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │

    │ │ │

    Comments are embedded in the following example:

    %% File: person.hrl
    │ │ │  
    │ │ │ @@ -230,48 +230,48 @@
    │ │ │  %%    name:  A string (default is undefined).
    │ │ │  %%    age:   An integer (default is undefined).
    │ │ │  %%    phone: A list of integers (default is []).
    │ │ │  %%    dict:  A dictionary containing various information
    │ │ │  %%           about the person.
    │ │ │  %%           A {Key, Value} list (default is the empty list).
    │ │ │  %%------------------------------------------------------------
    │ │ │ --record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ --include("person.hrl").
    │ │ │ --compile(export_all). % For test purposes only.
    │ │ │ +-record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ +-include("person.hrl").
    │ │ │ +-compile(export_all). % For test purposes only.
    │ │ │  
    │ │ │  %% This creates an instance of a person.
    │ │ │  %%   Note: The phone number is not supplied so the
    │ │ │  %%         default value [] will be used.
    │ │ │  
    │ │ │ -make_hacker_without_phone(Name, Age) ->
    │ │ │ -   #person{name = Name, age = Age,
    │ │ │ -           dict = [{computer_knowledge, excellent},
    │ │ │ -                   {drinks, coke}]}.
    │ │ │ +make_hacker_without_phone(Name, Age) ->
    │ │ │ +   #person{name = Name, age = Age,
    │ │ │ +           dict = [{computer_knowledge, excellent},
    │ │ │ +                   {drinks, coke}]}.
    │ │ │  
    │ │ │  %% This demonstrates matching in arguments
    │ │ │  
    │ │ │ -print(#person{name = Name, age = Age,
    │ │ │ -              phone = Phone, dict = Dict}) ->
    │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │ +print(#person{name = Name, age = Age,
    │ │ │ +              phone = Phone, dict = Dict}) ->
    │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │  
    │ │ │  %% Demonstrates type testing, selector, updating.
    │ │ │  
    │ │ │ -birthday(P) when is_record(P, person) ->
    │ │ │ -   P#person{age = P#person.age + 1}.
    │ │ │ +birthday(P) when is_record(P, person) ->
    │ │ │ +   P#person{age = P#person.age + 1}.
    │ │ │  
    │ │ │ -register_two_hackers() ->
    │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ -   OldHacker = birthday(Hacker1),
    │ │ │ +register_two_hackers() ->
    │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ +   OldHacker = birthday(Hacker1),
    │ │ │     % The central_register_server should have
    │ │ │     % an interface function for this.
    │ │ │ -   central_register_server ! {register_person, Hacker1},
    │ │ │ -   central_register_server ! {register_person,
    │ │ │ -             OldHacker#person{name = "Robert",
    │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │ +
    central_register_server ! {register_person, Hacker1}, │ │ │ + central_register_server ! {register_person, │ │ │ + OldHacker#person{name = "Robert", │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Header Files │ │ │

    │ │ │

    As shown above, some files have extension .hrl. These are header files that │ │ │ -are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ +are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ files in the messenger example. (manual).

    .hrl files can contain any valid Erlang code but are most often used for record │ │ │ and macro definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │

    │ │ │ -

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ +

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ creating a record, it gets the value of the atom undefined. (manual)

    Pattern matching with records is very similar to creating records. For example, │ │ │ -inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ +inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │

    │ │ │

    Another thing that has been added to the messenger is a macro. The file │ │ │ mess_config.hrl contains the definition:

    %%% Configure the location of the server node,
    │ │ │ --define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ -messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ +-define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ +messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ of using macros with, for example, parameters.

    The three Erlang (.erl) files in the messenger example are individually │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ these files into the system when they are referred to during execution of the │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_functions.html │ │ │ @@ -120,51 +120,51 @@ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │

    A function declaration is a sequence of function clauses separated by │ │ │ semicolons, and terminated by a period (.).

    A function clause consists of a clause head and a clause body, separated by │ │ │ ->.

    A clause head consists of the function name, an argument list, and an optional │ │ │ -guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │      Body1;
    │ │ │  ...;
    │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │      BodyK.

    The function name is an atom. Each argument is a pattern.

    The number of arguments N is the arity of the function. A function is │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ functions with the same name and in the same module, but with different arities │ │ │ are two different functions.

    A function named f in module mod and with arity N is often denoted as │ │ │ mod:f/N.

    A clause body consists of a sequence of expressions separated by comma (,):

    Expr1,
    │ │ │  ...,
    │ │ │  ExprN

    Valid Erlang expressions and guard sequences are described in │ │ │ -Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ -    N * fact(N-1);     % first clause body
    │ │ │ +Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ +    N * fact(N-1);     % first clause body
    │ │ │  
    │ │ │ -fact(0) ->             % second clause head
    │ │ │ +fact(0) ->             % second clause head
    │ │ │      1.                 % second clause body

    │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │

    │ │ │

    When a function M:F/N is called, first the code for the function is located. │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ the function must be exported to be visible outside the module it is defined in.

    If the function is found, the function clauses are scanned sequentially until a │ │ │ clause is found that fulfills both of the following two conditions:

    1. The patterns in the clause head can be successfully matched against the given │ │ │ arguments.
    2. The guard sequence, if any, is true.

    If such a clause cannot be found, a function_clause runtime error occurs.

    If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ -expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ --export([fact/1]).
    │ │ │ +expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ +-export([fact/1]).
    │ │ │  
    │ │ │ -fact(N) when N > 0 ->
    │ │ │ -    N * fact(N - 1);
    │ │ │ -fact(0) ->
    │ │ │ +fact(N) when N > 0 ->
    │ │ │ +    N * fact(N - 1);
    │ │ │ +fact(0) ->
    │ │ │      1.

    Assume that you want to calculate the factorial for 1:

    1> mod:fact(1).

    Evaluation starts at the first clause. The pattern N is matched against │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ -bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ -1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ +bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ +1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ and the body is evaluated:

    1 * fact(0) =>
    │ │ │  1 * 1 =>
    │ │ │  1

    Evaluation has succeed and mod:fact(1) returns 1.

    If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ matches. A function_clause runtime error occurs.

    │ │ │ @@ -173,17 +173,17 @@ │ │ │ │ │ │ Tail recursion │ │ │

    │ │ │

    If the last expression of a function body is a function call, a │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ -stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ -    io:format("~w~n", [N]),
    │ │ │ -    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ +stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ +    io:format("~w~n", [N]),
    │ │ │ +    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ call to fact(N-1).

    │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │

    │ │ │ @@ -191,17 +191,17 @@ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ and ets.

    The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ -without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │ +without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │  3
    │ │ │ -2> atom_to_list('Erlang').
    │ │ │ +2> atom_to_list('Erlang').
    │ │ │  "Erlang"
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Process Creation │ │ │

    │ │ │ -

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ -  Module = Name = atom()
    │ │ │ -  Args = [Arg1,...,ArgN]
    │ │ │ -    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ +

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ +  Module = Name = atom()
    │ │ │ +  Args = [Arg1,...,ArgN]
    │ │ │ +    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ arguments are the elements of the (possible empty) Args argument list.

    There exist a number of different spawn BIFs:

    │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │

    │ │ │

    Besides addressing a process by using its pid, there are also BIFs for │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_records.html │ │ │ @@ -123,17 +123,17 @@ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │ │ │

    A record definition consists of the name of the record, followed by the field │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ -used.

    -record(Name, {Field1 [= Expr1],
    │ │ │ +used.

    -record(Name, {Field1 [= Expr1],
    │ │ │                 ...
    │ │ │ -               FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ + FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ not use any variables.

    A record definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ record.

    If a record is used in several modules, it is recommended that the record │ │ │ definition is placed in an include file.

    Change

    Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ necessary to use the shell built-in function rd/2.

    │ │ │ │ │ │ @@ -143,32 +143,32 @@ │ │ │

    │ │ │

    The following expression creates a new Name record where the value of each │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    The fields can be in any order, not necessarily the same order as in the record │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ default value instead.

    If several fields are to be assigned the same value, the following construction │ │ │ can be used:

    #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

    Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ -and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, Tab) ->
    │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

    │ │ │ +lookup(Name, Tab) -> │ │ │ + ets:match_object(Tab, #person{name=Name, _='_'}).

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │

    │ │ │
    Expr#Name.Field

    Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ -record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -get_person_name(Person) ->
    │ │ │ +get_person_name(Person) ->
    │ │ │      Person#person.name.

    The following expression returns the position of the specified field in the │ │ │ -tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, List) ->
    │ │ │ -    lists:keyfind(Name, #person.name, List).

    │ │ │ +lookup(Name, List) -> │ │ │ + lists:keyfind(Name, #person.name, List).

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │

    │ │ │
    Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

    Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ @@ -178,51 +178,51 @@ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │ │ │

    Since record expressions are expanded to tuple expressions, creating │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ -expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │ +expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │      ...
    │ │ │  
    │ │ │ -handle(Msg, State) when State#state.running =:= true ->
    │ │ │ -    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │ +handle(Msg, State) when State#state.running =:= true ->
    │ │ │ +    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │      true;
    │ │ │ -is_person(_P) ->
    │ │ │ +is_person(_P) ->
    │ │ │      false.

    │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │

    │ │ │

    A pattern that matches a certain record is created in the same way as a record │ │ │ is created:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    In this case, one or more of Expr1 ... ExprK can be unbound variables.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │ -

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │ +

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │  
    │ │ │ -N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │ +N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

    which is equivalent to:

    "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
    │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

    Change

    Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ nested records.

    │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │

    │ │ │

    Record expressions are translated to tuple expressions during compilation. A │ │ │ -record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ -obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ -record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ +record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ +obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ +record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ number of fields.

    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    gen_server, simple code replacement is not sufficient. │ │ │ Instead, it is necessary to:

    • Suspend the processes using the module (to avoid that they try to handle any │ │ │ requests before the code replacement is completed).
    • Ask them to transform the internal state format and switch to the new version │ │ │ of the module.
    • Remove the old version.
    • Resume the processes.

    This is called synchronized code replacement and for this the following │ │ │ -instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ -{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ +instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ +{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ state of a behaviour as described above. It causes behaviour processes to call │ │ │ the callback function code_change/3, passing the term Extra and some other │ │ │ information as arguments. See the manual pages for the respective behaviours and │ │ │ Appup Cookbook.

    update with argument supervisor is used when changing the start │ │ │ specification of a supervisor. See Appup Cookbook.

    When a module is to be updated, the release handler finds which processes that │ │ │ are using the module by traversing the supervision tree of each running │ │ │ -application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ +application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ specification for the process.

    If Modules=dynamic, which is the case for event managers, the event manager │ │ │ process informs the release handler about the list of currently installed event │ │ │ handlers (gen_event), and it is checked if the module name is in this list │ │ │ instead.

    The release handler suspends, asks for code change, and resumes processes by │ │ │ calling the functions sys:suspend/1,2, sys:change_code/4,5, and │ │ │ sys:resume/1,2, respectively.

    │ │ │ │ │ │ │ │ │ │ │ │ add_module and delete_module │ │ │

    │ │ │ -

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ +

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ embedded mode it is necessary to use this this instruction. It is not │ │ │ strictly required when running Erlang in interactive mode, since the │ │ │ -code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ +code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ killed when the instruction is evaluated. Therefore, the user must │ │ │ ensure that all such processes are terminated before deleting module │ │ │ Module to avoid a situation with failing supervisor restarts.

    │ │ │ │ │ │ │ │ │ │ │ │ Application Instructions │ │ │ @@ -341,60 +341,60 @@ │ │ │ .app file.
  • Each UpFromVsn is a previous version of the application to upgrade from.
  • Each DownToVsn is a previous version of the application to downgrade to.
  • Each Instructions is a list of release handling instructions.
  • UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ appup in SASL.

    Appup Cookbook includes examples of .appup files for │ │ │ typical upgrade/downgrade cases.

    Example: Consider the release ch_rel-1 from │ │ │ Releases. Assume you want to add a function │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ -directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([available/0]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([available/0]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -available() ->
    │ │ │ -    gen_server:call(ch3, available).
    │ │ │ +available() ->
    │ │ │ +    gen_server:call(ch3, available).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2};
    │ │ │ -handle_call(available, _From, Chs) ->
    │ │ │ -    N = available(Chs),
    │ │ │ -    {reply, N, Chs}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2};
    │ │ │ +handle_call(available, _From, Chs) ->
    │ │ │ +    N = available(Chs),
    │ │ │ +    {reply, N, Chs}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ -updated:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "2"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ +updated:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "2"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ + [{"1", [{load_module, ch3}]}],
    │ │ │ + [{"1", [{load_module, ch3}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │

    │ │ │

    To define how to upgrade/downgrade between the new version and previous versions │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ @@ -405,22 +405,22 @@ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ transformed into a single list of low-level instructions in the right order.

    If the relup file is relatively simple, it can be created manually. It is only │ │ │ to contain low-level instructions.

    For details about the syntax and contents of the release upgrade file, see │ │ │ relup in SASL.

    Example, continued from the previous section: You have a new version "2" of │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ -changed from "A" to "B":

    {release,
    │ │ │ - {"ch_rel", "B"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "2"}]
    │ │ │ -}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │ +changed from "A" to "B":

    {release,
    │ │ │ + {"ch_rel", "B"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "2"}]
    │ │ │ +}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │  ok

    This generates a relup file with instructions for how to upgrade from version │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ "B" to version "A".

    Both the old and new versions of the .app and .rel files must be in the code │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ extended by using the option path:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
    │ │ │  [{path,["../ch_rel-1",
    │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
    │ │ │ @@ -433,25 +433,25 @@
    │ │ │  

    When you have made a new version of a release, a release package can be created │ │ │ with this new version and transferred to the target environment.

    To install the new version of the release in runtime, the release │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ which handles unpacking, installation, and removal of release │ │ │ packages. The release_handler module communicates with this process.

    Assuming there is an operational target system with installation root directory │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ to $ROOT/releases.

    First, unpack the release package. The files are then extracted from the │ │ │ -package:

    release_handler:unpack_release(ReleaseName) => {ok, Vsn}
    • ReleaseName is the name of the release package except the .tar.gz │ │ │ +package:

      release_handler:unpack_release(ReleaseName) => {ok, Vsn}
      • ReleaseName is the name of the release package except the .tar.gz │ │ │ extension.
      • Vsn is the version of the unpacked release, as defined in its .rel file.

      A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ placed. For applications with new version numbers, the application directories │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

      An unpacked release can be installed. The release handler then evaluates the │ │ │ -instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ +instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ the new version of the release, but if anything happens and the system is │ │ │ rebooted, it starts using the previous version again.

      To be made the default version, the newly installed release must be made │ │ │ permanent, which means the previous version becomes old:

      release_handler:make_permanent(Vsn) => ok

      The system keeps information about which versions are old and permanent in the │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ directory, are removed.

      release_handler:remove_release(Vsn) => ok

      │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ @@ -462,17 +462,17 @@ │ │ │ is needed, the file is to contain the empty list:

      [].

      Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ and config file is enough for illustration purposes:

      % cd $ROOT
      │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
      │ │ │  ...

      $ROOT is the installation directory of the target system.

      Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ sys.config and the relup file. For more information, see │ │ │ -Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │ +Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │  ok
      │ │ │ -2> systools:make_tar("ch_rel-2").
      │ │ │ +2> systools:make_tar("ch_rel-2").
      │ │ │  ok

      The new release package now also contains version "2" of ch_app and the │ │ │ relup file:

      % tar tf ch_rel-2.tar
      │ │ │  lib/kernel-9.2.4/ebin/kernel.app
      │ │ │  lib/kernel-9.2.4/ebin/application.beam
      │ │ │  ...
      │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
      │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
      │ │ │ @@ -485,31 +485,31 @@
      │ │ │  lib/ch_app-2/ebin/ch_sup.beam
      │ │ │  lib/ch_app-2/ebin/ch3.beam
      │ │ │  releases/B/start.boot
      │ │ │  releases/B/relup
      │ │ │  releases/B/sys.config
      │ │ │  releases/B/ch_rel-2.rel
      │ │ │  releases/ch_rel-2.rel

      Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ -directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ -{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ +directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ +{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ they have not changed.

      Under $ROOT/releases, a new directory B is created, containing │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │  ** exception error: undefined function ch3:available/0

      Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ -function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ -{ok,"A",[]}
      │ │ │ -4> ch3:available().
      │ │ │ +function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ +{ok,"A",[]}
      │ │ │ +4> ch3:available().
      │ │ │  3
      │ │ │ -5> code:which(ch3).
      │ │ │ +5> code:which(ch3).
      │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
      │ │ │ -6> code:which(ch_sup).
      │ │ │ +6> code:which(ch_sup).
      │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

      Processes in ch_app for which code have not been updated, for example, the │ │ │ supervisor, are still evaluating code from ch_app-1.

      Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │  ok

      │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │

      │ │ │

      When a new version of a release is installed, the application specifications are │ │ │ @@ -518,15 +518,15 @@ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ release package itself.

      Specifically, the application configuration parameters are automatically updated │ │ │ according to (in increasing priority order):

      • The data in the boot script, fetched from the new application resource file │ │ │ App.app
      • The new sys.config
      • Command-line arguments -App Par Val

      This means that parameter values set in the other system configuration files and │ │ │ values set using application:set_env/3 are disregarded.

      When an installed release is made permanent, the system process init is set to │ │ │ point out the new sys.config.

      After the installation, the application controller compares the old and new │ │ │ configuration parameters for all running applications and call the callback │ │ │ -function:

      Module:config_change(Changed, New, Removed)
      • Module is the application callback module as defined by the mod key in the │ │ │ +function:

        Module:config_change(Changed, New, Removed)
        • Module is the application callback module as defined by the mod key in the │ │ │ .app file.
        • Changed and New are lists of {Par,Val} for all changed and added │ │ │ configuration parameters, respectively.
        • Removed is a list of all parameters Par that have been removed.

        The function is optional and can be omitted when implementing an application │ │ │ callback module.

        │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/release_structure.html │ │ │ @@ -136,37 +136,37 @@ │ │ │ │ │ │ │ │ │ │ │ │ Release Resource File │ │ │ │ │ │

    To define a release, create a release resource file, or in short a .rel │ │ │ file. In the file, specify the name and version of the release, which ERTS │ │ │ -version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ - [{Application1, AppVsn1},
    │ │ │ +version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ + [{Application1, AppVsn1},
    │ │ │     ...
    │ │ │ -  {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ + {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ included in the release. The minimal release based on Erlang/OTP consists of the │ │ │ Kernel and STDLIB applications, so these applications must be included in the │ │ │ list.

    If the release is to be upgraded, it must also include the SASL application.

    Here is an example showing the .app file for a release of ch_app from │ │ │ -the Applications section:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ -applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ - {"ch_rel", "A"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "1"}]
    │ │ │ -}.

    │ │ │ +the Applications section:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ +applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ + {"ch_rel", "A"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "1"}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Generating Boot Scripts │ │ │

    │ │ │

    systools in the SASL application includes tools to build and check │ │ │ releases. The functions read the .rel and .app files and perform │ │ │ @@ -190,17 +190,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Release Package │ │ │ │ │ │

    The systools:make_tar/1,2 function takes a │ │ │ .rel file as input and creates a zipped tar file with the code for │ │ │ -the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │ +the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │  ok
    │ │ │ -2> systools:make_tar("ch_rel-1").
    │ │ │ +2> systools:make_tar("ch_rel-1").
    │ │ │  ok

    The release package by default contains:

    • The .app files
    • The .rel file
    • The object code for all applications, structured according to the │ │ │ application directory structure
    • The binary boot script renamed to start.boot
    % tar tf ch_rel-1.tar
    │ │ │  lib/kernel-9.2.4/ebin/kernel.app
    │ │ │  lib/kernel-9.2.4/ebin/application.beam
    │ │ │  ...
    │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
    │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/robustness.html
    │ │ │ @@ -128,68 +128,68 @@
    │ │ │  
    │ │ │  

    Before improving the messenger program, let us look at some general principles, │ │ │ using the ping pong program as an example. Recall that when "ping" finishes, it │ │ │ tells "pong" that it has done so by sending the atom finished as a message to │ │ │ "pong" so that "pong" can also finish. Another way to let "pong" finish is to │ │ │ make "pong" exit if it does not receive a message from ping within a certain │ │ │ time. This can be done by adding a time-out to pong as shown in the │ │ │ -following example:

    -module(tut19).
    │ │ │ +following example:

    -module(tut19).
    │ │ │  
    │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(0, Pong_Node) ->
    │ │ │ -    io:format("ping finished~n", []);
    │ │ │ +ping(0, Pong_Node) ->
    │ │ │ +    io:format("ping finished~n", []);
    │ │ │  
    │ │ │ -ping(N, Pong_Node) ->
    │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ +ping(N, Pong_Node) ->
    │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ +pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      after 5000 ->
    │ │ │ -            io:format("Pong timed out~n", [])
    │ │ │ +            io:format("Pong timed out~n", [])
    │ │ │      end.
    │ │ │  
    │ │ │ -start_pong() ->
    │ │ │ -    register(pong, spawn(tut19, pong, [])).
    │ │ │ +start_pong() ->
    │ │ │ +    register(pong, spawn(tut19, pong, [])).
    │ │ │  
    │ │ │ -start_ping(Pong_Node) ->
    │ │ │ -    spawn(tut19, ping, [3, Pong_Node]).

    After this is compiled and the file tut19.beam is copied to the necessary │ │ │ +start_ping(Pong_Node) -> │ │ │ + spawn(tut19, ping, [3, Pong_Node]).

    After this is compiled and the file tut19.beam is copied to the necessary │ │ │ directories, the following is seen on (pong@kosken):

    (pong@kosken)1> tut19:start_pong().
    │ │ │  true
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong timed out

    And the following is seen on (ping@gollum):

    (ping@gollum)1> tut19:start_ping(pong@kosken).
    │ │ │  <0.36.0>
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │ -ping finished

    The time-out is set in:

    pong() ->
    │ │ │ +ping finished

    The time-out is set in:

    pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      after 5000 ->
    │ │ │ -            io:format("Pong timed out~n", [])
    │ │ │ +            io:format("Pong timed out~n", [])
    │ │ │      end.

    The time-out (after 5000) is started when receive is entered. The time-out │ │ │ is canceled if {ping,Ping_PID} is received. If {ping,Ping_PID} is not │ │ │ received, the actions following the time-out are done after 5000 milliseconds. │ │ │ after must be last in the receive, that is, preceded by all other message │ │ │ reception specifications in the receive. It is also possible to call a │ │ │ -function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ +function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ distributed Erlang system. Time-outs are usually appropriate to supervise │ │ │ external events, for example, if you have expected a message from some external │ │ │ system within a specified time. For example, a time-out can be used to log a │ │ │ user out of the messenger system if they have not accessed it for, say, ten │ │ │ minutes.

    │ │ │ │ │ │ │ │ │ @@ -209,96 +209,96 @@ │ │ │ something called a signal to all the processes it has links to.

    The signal carries information about the pid it was sent from and the exit │ │ │ reason.

    The default behaviour of a process that receives a normal exit is to ignore the │ │ │ signal.

    The default behaviour in the two other cases (that is, abnormal exit) above is │ │ │ to:

    • Bypass all messages to the receiving process.
    • Kill the receiving process.
    • Propagate the same error signal to the links of the killed process.

    In this way you can connect all processes in a transaction together using links. │ │ │ If one of the processes exits abnormally, all the processes in the transaction │ │ │ are killed. As it is often wanted to create a process and link to it at the same │ │ │ time, there is a special BIF, spawn_link that does the │ │ │ -same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │ +same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ +pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut20, pong, []),
    │ │ │ -    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut20, pong, []),
    │ │ │ +    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │  Pong received ping
    │ │ │  <3820.41.0>
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong

    This is a slight modification of the ping pong program where both processes are │ │ │ spawned from the same start/1 function, and the "ping" process can be spawned │ │ │ on a separate node. Notice the use of the link BIF. "Ping" calls │ │ │ exit(ping) when it finishes and this causes an exit signal to be │ │ │ sent to "pong", which also terminates.

    It is possible to modify the default behaviour of a process so that it does not │ │ │ get killed when it receives abnormal exit signals. Instead, all signals are │ │ │ turned into normal messages on the format {'EXIT',FromPID,Reason} and added to │ │ │ -the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ +the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ Changing the default behaviour of a process in this way is usually not done in │ │ │ standard user programs, but is left to the supervisory programs in OTP. However, │ │ │ -the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │ +the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    pong1().
    │ │ │ +pong() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    pong1().
    │ │ │  
    │ │ │ -pong1() ->
    │ │ │ +pong1() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong1();
    │ │ │ -        {'EXIT', From, Reason} ->
    │ │ │ -            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │ +            pong1();
    │ │ │ +        {'EXIT', From, Reason} ->
    │ │ │ +            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut21, pong, []),
    │ │ │ -    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut21, pong, []),
    │ │ │ +    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │  <3820.39.0>
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │ @@ -351,135 +351,135 @@
    │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │  %%% To client: logoff
    │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │  %%%
    │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │  %%% name of the node where the messenger server runs
    │ │ │  
    │ │ │ --module(messenger).
    │ │ │ --export([start_server/0, server/0,
    │ │ │ -         logon/1, logoff/0, message/2, client/2]).
    │ │ │ +-module(messenger).
    │ │ │ +-export([start_server/0, server/0,
    │ │ │ +         logon/1, logoff/0, message/2, client/2]).
    │ │ │  
    │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │  %%% messenger server runs
    │ │ │ -server_node() ->
    │ │ │ +server_node() ->
    │ │ │      messenger@super.
    │ │ │  
    │ │ │  %%% This is the server process for the "messenger"
    │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ -server() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    server([]).
    │ │ │ +server() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    server([]).
    │ │ │  
    │ │ │ -server(User_List) ->
    │ │ │ +server(User_List) ->
    │ │ │      receive
    │ │ │ -        {From, logon, Name} ->
    │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {'EXIT', From, _} ->
    │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ -            server(User_List)
    │ │ │ +        {From, logon, Name} ->
    │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {'EXIT', From, _} ->
    │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ +            server(User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Start the server
    │ │ │ -start_server() ->
    │ │ │ -    register(messenger, spawn(messenger, server, [])).
    │ │ │ +start_server() ->
    │ │ │ +    register(messenger, spawn(messenger, server, [])).
    │ │ │  
    │ │ │  %%% Server adds a new user to the user list
    │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │      %% check if logged on anywhere else
    │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │          true ->
    │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │              User_List;
    │ │ │          false ->
    │ │ │ -            From ! {messenger, logged_on},
    │ │ │ -            link(From),
    │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ +            From ! {messenger, logged_on},
    │ │ │ +            link(From),
    │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Server deletes a user from the user list
    │ │ │ -server_logoff(From, User_List) ->
    │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ +server_logoff(From, User_List) ->
    │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │  
    │ │ │  
    │ │ │  %%% Server transfers a message between user
    │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │      %% check that the user is logged on and who he is
    │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ -        {value, {_, Name}} ->
    │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ +        {value, {_, Name}} ->
    │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% If the user exists, send the message
    │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │      %% Find the receiver and send the message
    │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ -            From ! {messenger, sent}
    │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ +            From ! {messenger, sent}
    │ │ │      end.
    │ │ │  
    │ │ │  %%% User Commands
    │ │ │ -logon(Name) ->
    │ │ │ -    case whereis(mess_client) of
    │ │ │ +logon(Name) ->
    │ │ │ +    case whereis(mess_client) of
    │ │ │          undefined ->
    │ │ │ -            register(mess_client,
    │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ +            register(mess_client,
    │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │          _ -> already_logged_on
    │ │ │      end.
    │ │ │  
    │ │ │ -logoff() ->
    │ │ │ +logoff() ->
    │ │ │      mess_client ! logoff.
    │ │ │  
    │ │ │ -message(ToName, Message) ->
    │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ +message(ToName, Message) ->
    │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │          undefined ->
    │ │ │              not_logged_on;
    │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │               ok
    │ │ │  end.
    │ │ │  
    │ │ │  %%% The client process which runs on each user node
    │ │ │ -client(Server_Node, Name) ->
    │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ -    await_result(),
    │ │ │ -    client(Server_Node).
    │ │ │ +client(Server_Node, Name) ->
    │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ +    await_result(),
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │ -client(Server_Node) ->
    │ │ │ +client(Server_Node) ->
    │ │ │      receive
    │ │ │          logoff ->
    │ │ │ -            exit(normal);
    │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ -            await_result();
    │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ +            exit(normal);
    │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ +            await_result();
    │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │      end,
    │ │ │ -    client(Server_Node).
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │  %%% wait for a response from the server
    │ │ │ -await_result() ->
    │ │ │ +await_result() ->
    │ │ │      receive
    │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ -            exit(normal);
    │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ -            io:format("~p~n", [What])
    │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ +            exit(normal);
    │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ +            io:format("~p~n", [What])
    │ │ │      after 5000 ->
    │ │ │ -            io:format("No response from server~n", []),
    │ │ │ -            exit(timeout)
    │ │ │ +            io:format("No response from server~n", []),
    │ │ │ +            exit(timeout)
    │ │ │      end.

    The following changes are added:

    The messenger server traps exits. If it receives an exit signal, │ │ │ {'EXIT',From,Reason}, this means that a client process has terminated or is │ │ │ unreachable for one of the following reasons:

    • The user has logged off (the "logoff" message is removed).
    • The network connection to the client is broken.
    • The node on which the client process resides has gone down.
    • The client processes has done some illegal operation.

    If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ the servers User_List using the server_logoff function. If the node on which │ │ │ the server runs goes down, an exit signal (automatically generated by the │ │ │ system) is sent to all of the client processes: │ │ │ {'EXIT',MessengerPID,noconnection} causing all the client processes to │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/search.html │ │ │ @@ -108,15 +108,15 @@ │ │ │

    │ │ │ - │ │ │ + │ │ │
    │ │ │

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/seq_prog.html │ │ │ @@ -136,293 +136,293 @@ │ │ │ 7 │ │ │ 2>

    As shown, the Erlang shell numbers the lines that can be entered, (as 1> 2>) and │ │ │ that it correctly says that 2 + 5 is 7. If you make writing mistakes in the │ │ │ shell, you can delete with the backspace key, as in most shells. There are many │ │ │ more editing commands in the shell (see │ │ │ tty - A command line interface in ERTS User's Guide).

    (Notice that many line numbers given by the shell in the following examples are │ │ │ out of sequence. This is because this tutorial was written and code-tested in │ │ │ -separate sessions).

    Here is a bit more complex calculation:

    2> (42 + 77) * 66 / 3.
    │ │ │ +separate sessions).

    Here is a bit more complex calculation:

    2> (42 + 77) * 66 / 3.
    │ │ │  2618.0

    Notice the use of brackets, the multiplication operator *, and the division │ │ │ operator /, as in normal arithmetic (see │ │ │ Expressions).

    Press Control-C to shut down the Erlang system and the Erlang shell.

    The following output is shown:

    BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
    │ │ │         (v)ersion (k)ill (D)b-tables (d)istribution
    │ │ │  a
    │ │ │ -$

    Type a to leave the Erlang system.

    Another way to shut down the Erlang system is by entering halt/0:

    3> halt().
    │ │ │ +$

    Type a to leave the Erlang system.

    Another way to shut down the Erlang system is by entering halt/0:

    3> halt().
    │ │ │  $

    │ │ │ │ │ │ │ │ │ │ │ │ Modules and Functions │ │ │

    │ │ │

    A programming language is not much use if you only can run code from the shell. │ │ │ So here is a small Erlang program. Enter it into a file named tut.erl using a │ │ │ suitable text editor. The file name tut.erl is important, and also that it is │ │ │ in the same directory as the one where you started erl). If you are lucky your │ │ │ editor has an Erlang mode that makes it easier for you to enter and format your │ │ │ code nicely (see The Erlang mode for Emacs │ │ │ in Tools User's Guide), but you can manage perfectly well without. Here is the │ │ │ -code to enter:

    -module(tut).
    │ │ │ --export([double/1]).
    │ │ │ +code to enter:

    -module(tut).
    │ │ │ +-export([double/1]).
    │ │ │  
    │ │ │ -double(X) ->
    │ │ │ +double(X) ->
    │ │ │      2 * X.

    It is not hard to guess that this program doubles the value of numbers. The │ │ │ first two lines of the code are described later. Let us compile the program. │ │ │ -This can be done in an Erlang shell as follows, where c means compile:

    3> c(tut).
    │ │ │ -{ok,tut}

    The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ +This can be done in an Erlang shell as follows, where c means compile:

    3> c(tut).
    │ │ │ +{ok,tut}

    The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ that there is some mistake in the text that you entered. Additional error │ │ │ messages gives an idea to what is wrong so you can modify the text and then try │ │ │ -to compile the program again.

    Now run the program:

    4> tut:double(10).
    │ │ │ +to compile the program again.

    Now run the program:

    4> tut:double(10).
    │ │ │  20

    As expected, double of 10 is 20.

    Now let us get back to the first two lines of the code. Erlang programs are │ │ │ written in files. Each file contains an Erlang module. The first line of code │ │ │ -in the module is the module name (see Modules):

    -module(tut).

    Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ +in the module is the module name (see Modules):

    -module(tut).

    Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ line. The files which are used to store the module must have the same name as │ │ │ the module but with the extension .erl. In this case the file name is │ │ │ tut.erl. When using a function in another module, the syntax │ │ │ module_name:function_name(arguments) is used. So the following means call │ │ │ -function double in module tut with argument 10.

    4> tut:double(10).

    The second line says that the module tut contains a function called double, │ │ │ -which takes one argument (X in our example):

    -export([double/1]).

    The second line also says that this function can be called from outside the │ │ │ +function double in module tut with argument 10.

    4> tut:double(10).

    The second line says that the module tut contains a function called double, │ │ │ +which takes one argument (X in our example):

    -export([double/1]).

    The second line also says that this function can be called from outside the │ │ │ module tut. More about this later. Again, notice the . at the end of the │ │ │ line.

    Now for a more complicated example, the factorial of a number. For example, the │ │ │ -factorial of 4 is 4 3 2 * 1, which equals 24.

    Enter the following code in a file named tut1.erl:

    -module(tut1).
    │ │ │ --export([fac/1]).
    │ │ │ +factorial of 4 is 4  3  2 * 1, which equals 24.

    Enter the following code in a file named tut1.erl:

    -module(tut1).
    │ │ │ +-export([fac/1]).
    │ │ │  
    │ │ │ -fac(1) ->
    │ │ │ +fac(1) ->
    │ │ │      1;
    │ │ │ -fac(N) ->
    │ │ │ -    N * fac(N - 1).

    So this is a module, called tut1 that contains a function called fac>, which │ │ │ -takes one argument, N.

    The first part says that the factorial of 1 is 1.:

    fac(1) ->
    │ │ │ +fac(N) ->
    │ │ │ +    N * fac(N - 1).

    So this is a module, called tut1 that contains a function called fac>, which │ │ │ +takes one argument, N.

    The first part says that the factorial of 1 is 1.:

    fac(1) ->
    │ │ │      1;

    Notice that this part ends with a semicolon ; that indicates that there is │ │ │ more of the function fac> to come.

    The second part says that the factorial of N is N multiplied by the factorial of │ │ │ -N - 1:

    fac(N) ->
    │ │ │ -    N * fac(N - 1).

    Notice that this part ends with a . saying that there are no more parts of │ │ │ -this function.

    Compile the file:

    5> c(tut1).
    │ │ │ -{ok,tut1}

    And now calculate the factorial of 4.

    6> tut1:fac(4).
    │ │ │ +N - 1:

    fac(N) ->
    │ │ │ +    N * fac(N - 1).

    Notice that this part ends with a . saying that there are no more parts of │ │ │ +this function.

    Compile the file:

    5> c(tut1).
    │ │ │ +{ok,tut1}

    And now calculate the factorial of 4.

    6> tut1:fac(4).
    │ │ │  24

    Here the function fac> in module tut1 is called with argument 4.

    A function can have many arguments. Let us expand the module tut1 with the │ │ │ -function to multiply two numbers:

    -module(tut1).
    │ │ │ --export([fac/1, mult/2]).
    │ │ │ +function to multiply two numbers:

    -module(tut1).
    │ │ │ +-export([fac/1, mult/2]).
    │ │ │  
    │ │ │ -fac(1) ->
    │ │ │ +fac(1) ->
    │ │ │      1;
    │ │ │ -fac(N) ->
    │ │ │ -    N * fac(N - 1).
    │ │ │ +fac(N) ->
    │ │ │ +    N * fac(N - 1).
    │ │ │  
    │ │ │ -mult(X, Y) ->
    │ │ │ +mult(X, Y) ->
    │ │ │      X * Y.

    Notice that it is also required to expand the -export line with the │ │ │ -information that there is another function mult with two arguments.

    Compile:

    7> c(tut1).
    │ │ │ -{ok,tut1}

    Try out the new function mult:

    8> tut1:mult(3,4).
    │ │ │ +information that there is another function mult with two arguments.

    Compile:

    7> c(tut1).
    │ │ │ +{ok,tut1}

    Try out the new function mult:

    8> tut1:mult(3,4).
    │ │ │  12

    In this example the numbers are integers and the arguments in the functions in │ │ │ the code N, X, and Y are called variables. Variables must start with a │ │ │ capital letter (see Variables). Examples of │ │ │ variables are Number, ShoeSize, and Age.

    │ │ │ │ │ │ │ │ │ │ │ │ Atoms │ │ │

    │ │ │

    Atom is another data type in Erlang. Atoms start with a small letter (see │ │ │ Atom), for example, charles, centimeter, and │ │ │ inch. Atoms are simply names, nothing else. They are not like variables, which │ │ │ can have a value.

    Enter the next program in a file named tut2.erl). It can be useful for │ │ │ -converting from inches to centimeters and conversely:

    -module(tut2).
    │ │ │ --export([convert/2]).
    │ │ │ +converting from inches to centimeters and conversely:

    -module(tut2).
    │ │ │ +-export([convert/2]).
    │ │ │  
    │ │ │ -convert(M, inch) ->
    │ │ │ +convert(M, inch) ->
    │ │ │      M / 2.54;
    │ │ │  
    │ │ │ -convert(N, centimeter) ->
    │ │ │ -    N * 2.54.

    Compile:

    9> c(tut2).
    │ │ │ -{ok,tut2}

    Test:

    10> tut2:convert(3, inch).
    │ │ │ +convert(N, centimeter) ->
    │ │ │ +    N * 2.54.

    Compile:

    9> c(tut2).
    │ │ │ +{ok,tut2}

    Test:

    10> tut2:convert(3, inch).
    │ │ │  1.1811023622047243
    │ │ │ -11> tut2:convert(7, centimeter).
    │ │ │ +11> tut2:convert(7, centimeter).
    │ │ │  17.78

    Notice the introduction of decimals (floating point numbers) without any │ │ │ explanation. Hopefully you can cope with that.

    Let us see what happens if something other than centimeter or inch is │ │ │ -entered in the convert function:

    12> tut2:convert(3, miles).
    │ │ │ +entered in the convert function:

    12> tut2:convert(3, miles).
    │ │ │  ** exception error: no function clause matching tut2:convert(3,miles) (tut2.erl, line 4)

    The two parts of the convert function are called its clauses. As shown, │ │ │ miles is not part of either of the clauses. The Erlang system cannot match │ │ │ either of the clauses so an error message function_clause is returned. The │ │ │ shell formats the error message nicely, but the error tuple is saved in the │ │ │ -shell's history list and can be output by the shell command v/1:

    13> v(12).
    │ │ │ -{'EXIT',{function_clause,[{tut2,convert,
    │ │ │ -                                [3,miles],
    │ │ │ -                                [{file,"tut2.erl"},{line,4}]},
    │ │ │ -                          {erl_eval,do_apply,6,
    │ │ │ -                                    [{file,"erl_eval.erl"},{line,677}]},
    │ │ │ -                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
    │ │ │ -                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
    │ │ │ -                          {shell,eval_loop,3,
    │ │ │ -                                 [{file,"shell.erl"},{line,627}]}]}}

    │ │ │ +shell's history list and can be output by the shell command v/1:

    13> v(12).
    │ │ │ +{'EXIT',{function_clause,[{tut2,convert,
    │ │ │ +                                [3,miles],
    │ │ │ +                                [{file,"tut2.erl"},{line,4}]},
    │ │ │ +                          {erl_eval,do_apply,6,
    │ │ │ +                                    [{file,"erl_eval.erl"},{line,677}]},
    │ │ │ +                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
    │ │ │ +                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
    │ │ │ +                          {shell,eval_loop,3,
    │ │ │ +                                 [{file,"shell.erl"},{line,627}]}]}}

    │ │ │ │ │ │ │ │ │ │ │ │ Tuples │ │ │

    │ │ │ -

    Now the tut2 program is hardly good programming style. Consider:

    tut2:convert(3, inch).

    Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ +

    Now the tut2 program is hardly good programming style. Consider:

    tut2:convert(3, inch).

    Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ is to be converted to inches? Erlang has a way to group things together to make │ │ │ things more understandable. These are called tuples and are surrounded by │ │ │ curly brackets, { and }.

    So, {inch,3} denotes 3 inches and {centimeter,5} denotes 5 centimeters. Now │ │ │ let us write a new program that converts centimeters to inches and conversely. │ │ │ -Enter the following code in a file called tut3.erl):

    -module(tut3).
    │ │ │ --export([convert_length/1]).
    │ │ │ +Enter the following code in a file called tut3.erl):

    -module(tut3).
    │ │ │ +-export([convert_length/1]).
    │ │ │  
    │ │ │ -convert_length({centimeter, X}) ->
    │ │ │ -    {inch, X / 2.54};
    │ │ │ -convert_length({inch, Y}) ->
    │ │ │ -    {centimeter, Y * 2.54}.

    Compile and test:

    14> c(tut3).
    │ │ │ -{ok,tut3}
    │ │ │ -15> tut3:convert_length({inch, 5}).
    │ │ │ -{centimeter,12.7}
    │ │ │ -16> tut3:convert_length(tut3:convert_length({inch, 5})).
    │ │ │ -{inch,5.0}

    Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ +convert_length({centimeter, X}) -> │ │ │ + {inch, X / 2.54}; │ │ │ +convert_length({inch, Y}) -> │ │ │ + {centimeter, Y * 2.54}.

    Compile and test:

    14> c(tut3).
    │ │ │ +{ok,tut3}
    │ │ │ +15> tut3:convert_length({inch, 5}).
    │ │ │ +{centimeter,12.7}
    │ │ │ +16> tut3:convert_length(tut3:convert_length({inch, 5})).
    │ │ │ +{inch,5.0}

    Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ reassuringly get back to the original value. That is, the argument to a function │ │ │ can be the result of another function. Consider how line 16 (above) works. The │ │ │ argument given to the function {inch,5} is first matched against the first │ │ │ head clause of convert_length, that is, convert_length({centimeter,X}). It │ │ │ can be seen that {centimeter,X} does not match {inch,5} (the head is the bit │ │ │ before the ->). This having failed, let us try the head of the next clause │ │ │ that is, convert_length({inch,Y}). This matches, and Y gets the value 5.

    Tuples can have more than two parts, in fact as many parts as you want, and │ │ │ contain any valid Erlang term. For example, to represent the temperature of │ │ │ -various cities of the world:

    {moscow, {c, -10}}
    │ │ │ -{cape_town, {f, 70}}
    │ │ │ -{paris, {f, 28}}

    Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ +various cities of the world:

    {moscow, {c, -10}}
    │ │ │ +{cape_town, {f, 70}}
    │ │ │ +{paris, {f, 28}}

    Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ element. In the tuple {moscow,{c,-10}}, element 1 is moscow and element 2 │ │ │ is {c,-10}. Here c represents Celsius and f Fahrenheit.

    │ │ │ │ │ │ │ │ │ │ │ │ Lists │ │ │

    │ │ │

    Whereas tuples group things together, it is also needed to represent lists of │ │ │ things. Lists in Erlang are surrounded by square brackets, [ and ]. For │ │ │ -example, a list of the temperatures of various cities in the world can be:

    [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
    │ │ │ - {paris, {f, 28}}, {london, {f, 36}}]

    Notice that this list was so long that it did not fit on one line. This does not │ │ │ +example, a list of the temperatures of various cities in the world can be:

    [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
    │ │ │ + {paris, {f, 28}}, {london, {f, 36}}]

    Notice that this list was so long that it did not fit on one line. This does not │ │ │ matter, Erlang allows line breaks at all "sensible places" but not, for example, │ │ │ in the middle of atoms, integers, and others.

    A useful way of looking at parts of lists, is by using |. This is best │ │ │ -explained by an example using the shell:

    17> [First |TheRest] = [1,2,3,4,5].
    │ │ │ -[1,2,3,4,5]
    │ │ │ +explained by an example using the shell:

    17> [First |TheRest] = [1,2,3,4,5].
    │ │ │ +[1,2,3,4,5]
    │ │ │  18> First.
    │ │ │  1
    │ │ │  19> TheRest.
    │ │ │ -[2,3,4,5]

    To separate the first elements of the list from the rest of the list, | is │ │ │ -used. First has got value 1 and TheRest has got the value [2,3,4,5].

    Another example:

    20> [E1, E2 | R] = [1,2,3,4,5,6,7].
    │ │ │ -[1,2,3,4,5,6,7]
    │ │ │ +[2,3,4,5]

    To separate the first elements of the list from the rest of the list, | is │ │ │ +used. First has got value 1 and TheRest has got the value [2,3,4,5].

    Another example:

    20> [E1, E2 | R] = [1,2,3,4,5,6,7].
    │ │ │ +[1,2,3,4,5,6,7]
    │ │ │  21> E1.
    │ │ │  1
    │ │ │  22> E2.
    │ │ │  2
    │ │ │  23> R.
    │ │ │ -[3,4,5,6,7]

    Here you see the use of | to get the first two elements from the list. If you │ │ │ +[3,4,5,6,7]

    Here you see the use of | to get the first two elements from the list. If you │ │ │ try to get more elements from the list than there are elements in the list, an │ │ │ error is returned. Notice also the special case of the list with no elements, │ │ │ -[]:

    24> [A, B | C] = [1, 2].
    │ │ │ -[1,2]
    │ │ │ +[]:

    24> [A, B | C] = [1, 2].
    │ │ │ +[1,2]
    │ │ │  25> A.
    │ │ │  1
    │ │ │  26> B.
    │ │ │  2
    │ │ │  27> C.
    │ │ │ -[]

    In the previous examples, new variable names are used, instead of reusing the │ │ │ +[]

    In the previous examples, new variable names are used, instead of reusing the │ │ │ old ones: First, TheRest, E1, E2, R, A, B, and C. The reason for │ │ │ this is that a variable can only be given a value once in its context (scope). │ │ │ More about this later.

    The following example shows how to find the length of a list. Enter the │ │ │ -following code in a file named tut4.erl:

    -module(tut4).
    │ │ │ +following code in a file named tut4.erl:

    -module(tut4).
    │ │ │  
    │ │ │ --export([list_length/1]).
    │ │ │ +-export([list_length/1]).
    │ │ │  
    │ │ │ -list_length([]) ->
    │ │ │ +list_length([]) ->
    │ │ │      0;
    │ │ │ -list_length([First | Rest]) ->
    │ │ │ -    1 + list_length(Rest).

    Compile and test:

    28> c(tut4).
    │ │ │ -{ok,tut4}
    │ │ │ -29> tut4:list_length([1,2,3,4,5,6,7]).
    │ │ │ -7

    Explanation:

    list_length([]) ->
    │ │ │ -    0;

    The length of an empty list is obviously 0.

    list_length([First | Rest]) ->
    │ │ │ -    1 + list_length(Rest).

    The length of a list with the first element First and the remaining elements │ │ │ +list_length([First | Rest]) -> │ │ │ + 1 + list_length(Rest).

    Compile and test:

    28> c(tut4).
    │ │ │ +{ok,tut4}
    │ │ │ +29> tut4:list_length([1,2,3,4,5,6,7]).
    │ │ │ +7

    Explanation:

    list_length([]) ->
    │ │ │ +    0;

    The length of an empty list is obviously 0.

    list_length([First | Rest]) ->
    │ │ │ +    1 + list_length(Rest).

    The length of a list with the first element First and the remaining elements │ │ │ Rest is 1 + the length of Rest.

    (Advanced readers only: This is not tail recursive, there is a better way to │ │ │ write this function.)

    In general, tuples are used where "records" or "structs" are used in other │ │ │ languages. Also, lists are used when representing things with varying sizes, │ │ │ that is, where linked lists are used in other languages.

    Erlang does not have a string data type. Instead, strings can be represented by │ │ │ lists of Unicode characters. This implies for example that the list [97,98,99] │ │ │ is equivalent to "abc". The Erlang shell is "clever" and guesses what list you │ │ │ -mean and outputs it in what it thinks is the most appropriate form, for example:

    30> [97,98,99].
    │ │ │ +mean and outputs it in what it thinks is the most appropriate form, for example:

    30> [97,98,99].
    │ │ │  "abc"

    │ │ │ │ │ │ │ │ │ │ │ │ Maps │ │ │

    │ │ │

    Maps are a set of key to value associations. These associations are encapsulated │ │ │ -with #{ and }. To create an association from "key" to value 42:

    > #{ "key" => 42 }.
    │ │ │ -#{"key" => 42}

    Let us jump straight into the deep end with an example using some interesting │ │ │ +with #{ and }. To create an association from "key" to value 42:

    > #{ "key" => 42 }.
    │ │ │ +#{"key" => 42}

    Let us jump straight into the deep end with an example using some interesting │ │ │ features.

    The following example shows how to calculate alpha blending using maps to │ │ │ -reference color and alpha channels. Enter the code in a file named color.erl):

    -module(color).
    │ │ │ +reference color and alpha channels. Enter the code in a file named color.erl):

    -module(color).
    │ │ │  
    │ │ │ --export([new/4, blend/2]).
    │ │ │ +-export([new/4, blend/2]).
    │ │ │  
    │ │ │ --define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
    │ │ │ +-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
    │ │ │  
    │ │ │ -new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.
    │ │ │ -
    │ │ │ -blend(Src,Dst) ->
    │ │ │ -    blend(Src,Dst,alpha(Src,Dst)).
    │ │ │ -
    │ │ │ -blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ -    Dst#{
    │ │ │ -        red   := red(Src,Dst) / Alpha,
    │ │ │ -        green := green(Src,Dst) / Alpha,
    │ │ │ -        blue  := blue(Src,Dst) / Alpha,
    │ │ │ +new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.
    │ │ │ +
    │ │ │ +blend(Src,Dst) ->
    │ │ │ +    blend(Src,Dst,alpha(Src,Dst)).
    │ │ │ +
    │ │ │ +blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ +    Dst#{
    │ │ │ +        red   := red(Src,Dst) / Alpha,
    │ │ │ +        green := green(Src,Dst) / Alpha,
    │ │ │ +        blue  := blue(Src,Dst) / Alpha,
    │ │ │          alpha := Alpha
    │ │ │ -    };
    │ │ │ -blend(_,Dst,_) ->
    │ │ │ -    Dst#{
    │ │ │ +    };
    │ │ │ +blend(_,Dst,_) ->
    │ │ │ +    Dst#{
    │ │ │          red   := 0.0,
    │ │ │          green := 0.0,
    │ │ │          blue  := 0.0,
    │ │ │          alpha := 0.0
    │ │ │ -    }.
    │ │ │ +    }.
    │ │ │  
    │ │ │ -alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ -    SA + DA*(1.0 - SA).
    │ │ │ +alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ +    SA + DA*(1.0 - SA).
    │ │ │  
    │ │ │ -red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
    │ │ │ -green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
    │ │ │ -blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

    Compile and test:

    > c(color).
    │ │ │ -{ok,color}
    │ │ │ -> C1 = color:new(0.3,0.4,0.5,1.0).
    │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ -> C2 = color:new(1.0,0.8,0.1,0.3).
    │ │ │ -#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
    │ │ │ -> color:blend(C1,C2).
    │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ -> color:blend(C2,C1).
    │ │ │ -#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

    This example warrants some explanation:

    -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

    First a macro is_channel is defined to help with the guard tests. This is only │ │ │ +red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ +green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ +blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA).

    Compile and test:

    > c(color).
    │ │ │ +{ok,color}
    │ │ │ +> C1 = color:new(0.3,0.4,0.5,1.0).
    │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ +> C2 = color:new(1.0,0.8,0.1,0.3).
    │ │ │ +#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
    │ │ │ +> color:blend(C1,C2).
    │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ +> color:blend(C2,C1).
    │ │ │ +#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

    This example warrants some explanation:

    -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

    First a macro is_channel is defined to help with the guard tests. This is only │ │ │ here for convenience and to reduce syntax cluttering. For more information about │ │ │ -macros, see The Preprocessor.

    new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.

    The function new/4 creates a new map term and lets the keys red, green, │ │ │ +macros, see The Preprocessor.

    new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.

    The function new/4 creates a new map term and lets the keys red, green, │ │ │ blue, and alpha be associated with an initial value. In this case, only │ │ │ float values between and including 0.0 and 1.0 are allowed, as ensured by the │ │ │ ?is_channel/1 macro for each argument. Only the => operator is allowed when │ │ │ creating a new map.

    By calling blend/2 on any color term created by new/4, the resulting color │ │ │ -can be calculated as determined by the two map terms.

    The first thing blend/2 does is to calculate the resulting alpha channel:

    alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ -    SA + DA*(1.0 - SA).

    The value associated with key alpha is fetched for both arguments using the │ │ │ +can be calculated as determined by the two map terms.

    The first thing blend/2 does is to calculate the resulting alpha channel:

    alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ +    SA + DA*(1.0 - SA).

    The value associated with key alpha is fetched for both arguments using the │ │ │ := operator. The other keys in the map are ignored, only the key alpha is │ │ │ -required and checked for.

    This is also the case for functions red/2, blue/2, and green/2.

    red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

    The difference here is that a check is made for two keys in each map argument. │ │ │ -The other keys are ignored.

    Finally, let us return the resulting color in blend/3:

    blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ -    Dst#{
    │ │ │ -        red   := red(Src,Dst) / Alpha,
    │ │ │ -        green := green(Src,Dst) / Alpha,
    │ │ │ -        blue  := blue(Src,Dst) / Alpha,
    │ │ │ +required and checked for.

    This is also the case for functions red/2, blue/2, and green/2.

    red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ +    SV*SA + DV*DA*(1.0 - SA).

    The difference here is that a check is made for two keys in each map argument. │ │ │ +The other keys are ignored.

    Finally, let us return the resulting color in blend/3:

    blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ +    Dst#{
    │ │ │ +        red   := red(Src,Dst) / Alpha,
    │ │ │ +        green := green(Src,Dst) / Alpha,
    │ │ │ +        blue  := blue(Src,Dst) / Alpha,
    │ │ │          alpha := Alpha
    │ │ │ -    };

    The Dst map is updated with new channel values. The syntax for updating an │ │ │ + };

    The Dst map is updated with new channel values. The syntax for updating an │ │ │ existing key with a new value is with the := operator.

    │ │ │ │ │ │ │ │ │ │ │ │ Standard Modules and Manual Pages │ │ │

    │ │ │

    Erlang has many standard modules to help you do things. For example, the module │ │ │ @@ -442,24 +442,24 @@ │ │ │ │ │ │ │ │ │ │ │ │ Writing Output to a Terminal │ │ │

    │ │ │

    It is nice to be able to do formatted output in examples, so the next example │ │ │ shows a simple way to use the io:format/2 function. Like all other exported │ │ │ -functions, you can test the io:format/2 function in the shell:

    31> io:format("hello world~n", []).
    │ │ │ +functions, you can test the io:format/2 function in the shell:

    31> io:format("hello world~n", []).
    │ │ │  hello world
    │ │ │  ok
    │ │ │ -32> io:format("this outputs one Erlang term: ~w~n", [hello]).
    │ │ │ +32> io:format("this outputs one Erlang term: ~w~n", [hello]).
    │ │ │  this outputs one Erlang term: hello
    │ │ │  ok
    │ │ │ -33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
    │ │ │ +33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
    │ │ │  this outputs two Erlang terms: helloworld
    │ │ │  ok
    │ │ │ -34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
    │ │ │ +34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
    │ │ │  this outputs two Erlang terms: hello world
    │ │ │  ok

    The function io:format/2 (that is, format with two arguments) takes two lists. │ │ │ The first one is nearly always a list written between " ". This list is printed │ │ │ out as it is, except that each ~w is replaced by a term taken in order from the │ │ │ second list. Each ~n is replaced by a new line. The io:format/2 function │ │ │ itself returns the atom ok if everything goes as planned. Like other functions │ │ │ in Erlang, it crashes if an error occurs. This is not a fault in Erlang, it is a │ │ │ @@ -473,34 +473,34 @@ │ │ │ A Larger Example │ │ │ │ │ │

    Now for a larger example to consolidate what you have learnt so far. Assume that │ │ │ you have a list of temperature readings from a number of cities in the world. │ │ │ Some of them are in Celsius and some in Fahrenheit (as in the previous list). │ │ │ First let us convert them all to Celsius, then let us print the data neatly.

    %% This module is in file tut5.erl
    │ │ │  
    │ │ │ --module(tut5).
    │ │ │ --export([format_temps/1]).
    │ │ │ +-module(tut5).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │  %% Only this function is exported
    │ │ │ -format_temps([])->                        % No output for an empty list
    │ │ │ +format_temps([])->                        % No output for an empty list
    │ │ │      ok;
    │ │ │ -format_temps([City | Rest]) ->
    │ │ │ -    print_temp(convert_to_celsius(City)),
    │ │ │ -    format_temps(Rest).
    │ │ │ -
    │ │ │ -convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
    │ │ │ -    {Name, {c, Temp}};
    │ │ │ -convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
    │ │ │ -    {Name, {c, (Temp - 32) * 5 / 9}}.
    │ │ │ -
    │ │ │ -print_temp({Name, {c, Temp}}) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]).
    35> c(tut5).
    │ │ │ -{ok,tut5}
    │ │ │ -36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +format_temps([City | Rest]) ->
    │ │ │ +    print_temp(convert_to_celsius(City)),
    │ │ │ +    format_temps(Rest).
    │ │ │ +
    │ │ │ +convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
    │ │ │ +    {Name, {c, Temp}};
    │ │ │ +convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
    │ │ │ +    {Name, {c, (Temp - 32) * 5 / 9}}.
    │ │ │ +
    │ │ │ +print_temp({Name, {c, Temp}}) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]).
    35> c(tut5).
    │ │ │ +{ok,tut5}
    │ │ │ +36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  ok

    Before looking at how this program works, notice that a few comments are added │ │ │ to the code. A comment starts with a %-character and goes on to the end of the │ │ │ @@ -528,28 +528,28 @@ │ │ │ │ │ │ │ │ │ │ │ │ Matching, Guards, and Scope of Variables │ │ │ │ │ │

    It can be useful to find the maximum and minimum temperature in lists like this. │ │ │ Before extending the program to do this, let us look at functions for finding │ │ │ -the maximum value of the elements in a list:

    -module(tut6).
    │ │ │ --export([list_max/1]).
    │ │ │ +the maximum value of the elements in a list:

    -module(tut6).
    │ │ │ +-export([list_max/1]).
    │ │ │  
    │ │ │ -list_max([Head|Rest]) ->
    │ │ │ -   list_max(Rest, Head).
    │ │ │ +list_max([Head|Rest]) ->
    │ │ │ +   list_max(Rest, Head).
    │ │ │  
    │ │ │ -list_max([], Res) ->
    │ │ │ +list_max([], Res) ->
    │ │ │      Res;
    │ │ │ -list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ -    list_max(Rest, Head);
    │ │ │ -list_max([Head|Rest], Result_so_far)  ->
    │ │ │ -    list_max(Rest, Result_so_far).
    37> c(tut6).
    │ │ │ -{ok,tut6}
    │ │ │ -38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
    │ │ │ +list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ +    list_max(Rest, Head);
    │ │ │ +list_max([Head|Rest], Result_so_far)  ->
    │ │ │ +    list_max(Rest, Result_so_far).
    37> c(tut6).
    │ │ │ +{ok,tut6}
    │ │ │ +38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
    │ │ │  7

    First notice that two functions have the same name, list_max. However, each of │ │ │ these takes a different number of arguments (parameters). In Erlang these are │ │ │ regarded as completely different functions. Where you need to distinguish │ │ │ between these functions, you write Name/Arity, where Name is the function name │ │ │ and Arity is the number of arguments, in this case list_max/1 and │ │ │ list_max/2.

    In this example you walk through a list "carrying" a value, in this case │ │ │ Result_so_far. list_max/1 simply assumes that the max value of the list is │ │ │ @@ -578,180 +578,180 @@ │ │ │ 5 │ │ │ 40> M = 6. │ │ │ ** exception error: no match of right hand side value 6 │ │ │ 41> M = M + 1. │ │ │ ** exception error: no match of right hand side value 6 │ │ │ 42> N = M + 1. │ │ │ 6

    The use of the match operator is particularly useful for pulling apart Erlang │ │ │ -terms and creating new ones.

    43> {X, Y} = {paris, {f, 28}}.
    │ │ │ -{paris,{f,28}}
    │ │ │ +terms and creating new ones.

    43> {X, Y} = {paris, {f, 28}}.
    │ │ │ +{paris,{f,28}}
    │ │ │  44> X.
    │ │ │  paris
    │ │ │  45> Y.
    │ │ │ -{f,28}

    Here X gets the value paris and Y the value {f,28}.

    If you try to do the same again with another city, an error is returned:

    46> {X, Y} = {london, {f, 36}}.
    │ │ │ +{f,28}

    Here X gets the value paris and Y the value {f,28}.

    If you try to do the same again with another city, an error is returned:

    46> {X, Y} = {london, {f, 36}}.
    │ │ │  ** exception error: no match of right hand side value {london,{f,36}}

    Variables can also be used to improve the readability of programs. For example, │ │ │ -in function list_max/2 above, you can write:

    list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ +in function list_max/2 above, you can write:

    list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │      New_result_far = Head,
    │ │ │ -    list_max(Rest, New_result_far);

    This is possibly a little clearer.

    │ │ │ + list_max(Rest, New_result_far);

    This is possibly a little clearer.

    │ │ │ │ │ │ │ │ │ │ │ │ More About Lists │ │ │

    │ │ │ -

    Remember that the | operator can be used to get the head of a list:

    47> [M1|T1] = [paris, london, rome].
    │ │ │ -[paris,london,rome]
    │ │ │ +

    Remember that the | operator can be used to get the head of a list:

    47> [M1|T1] = [paris, london, rome].
    │ │ │ +[paris,london,rome]
    │ │ │  48> M1.
    │ │ │  paris
    │ │ │  49> T1.
    │ │ │ -[london,rome]

    The | operator can also be used to add a head to a list:

    50> L1 = [madrid | T1].
    │ │ │ -[madrid,london,rome]
    │ │ │ +[london,rome]

    The | operator can also be used to add a head to a list:

    50> L1 = [madrid | T1].
    │ │ │ +[madrid,london,rome]
    │ │ │  51> L1.
    │ │ │ -[madrid,london,rome]

    Now an example of this when working with lists - reversing the order of a list:

    -module(tut8).
    │ │ │ +[madrid,london,rome]

    Now an example of this when working with lists - reversing the order of a list:

    -module(tut8).
    │ │ │  
    │ │ │ --export([reverse/1]).
    │ │ │ +-export([reverse/1]).
    │ │ │  
    │ │ │ -reverse(List) ->
    │ │ │ -    reverse(List, []).
    │ │ │ +reverse(List) ->
    │ │ │ +    reverse(List, []).
    │ │ │  
    │ │ │ -reverse([Head | Rest], Reversed_List) ->
    │ │ │ -    reverse(Rest, [Head | Reversed_List]);
    │ │ │ -reverse([], Reversed_List) ->
    │ │ │ -    Reversed_List.
    52> c(tut8).
    │ │ │ -{ok,tut8}
    │ │ │ -53> tut8:reverse([1,2,3]).
    │ │ │ -[3,2,1]

    Consider how Reversed_List is built. It starts as [], then successively the │ │ │ +reverse([Head | Rest], Reversed_List) -> │ │ │ + reverse(Rest, [Head | Reversed_List]); │ │ │ +reverse([], Reversed_List) -> │ │ │ + Reversed_List.

    52> c(tut8).
    │ │ │ +{ok,tut8}
    │ │ │ +53> tut8:reverse([1,2,3]).
    │ │ │ +[3,2,1]

    Consider how Reversed_List is built. It starts as [], then successively the │ │ │ heads are taken off of the list to be reversed and added to the the │ │ │ -Reversed_List, as shown in the following:

    reverse([1|2,3], []) =>
    │ │ │ -    reverse([2,3], [1|[]])
    │ │ │ +Reversed_List, as shown in the following:

    reverse([1|2,3], []) =>
    │ │ │ +    reverse([2,3], [1|[]])
    │ │ │  
    │ │ │ -reverse([2|3], [1]) =>
    │ │ │ -    reverse([3], [2|[1])
    │ │ │ +reverse([2|3], [1]) =>
    │ │ │ +    reverse([3], [2|[1])
    │ │ │  
    │ │ │ -reverse([3|[]], [2,1]) =>
    │ │ │ -    reverse([], [3|[2,1]])
    │ │ │ +reverse([3|[]], [2,1]) =>
    │ │ │ +    reverse([], [3|[2,1]])
    │ │ │  
    │ │ │ -reverse([], [3,2,1]) =>
    │ │ │ -    [3,2,1]

    The module lists contains many functions for manipulating lists, for example, │ │ │ +reverse([], [3,2,1]) => │ │ │ + [3,2,1]

    The module lists contains many functions for manipulating lists, for example, │ │ │ for reversing them. So before writing a list-manipulating function it is a good │ │ │ idea to check if one not already is written for you (see the lists manual │ │ │ page in STDLIB).

    Now let us get back to the cities and temperatures, but take a more structured │ │ │ -approach this time. First let us convert the whole list to Celsius as follows:

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ +approach this time. First let us convert the whole list to Celsius as follows:

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    convert_list_to_c(List_of_cities).
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    convert_list_to_c(List_of_cities).
    │ │ │  
    │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].

    Test the function:

    54> c(tut7).
    │ │ │ -{ok, tut7}.
    │ │ │ -55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {cape_town,{c,21.11111111111111}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2.2222222222222223}},
    │ │ │ - {london,{c,2.2222222222222223}}]

    Explanation:

    format_temps(List_of_cities) ->
    │ │ │ -    convert_list_to_c(List_of_cities).

    Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) -> │ │ │ + Converted_City = {Name, {c, (F -32)* 5 / 9}}, │ │ │ + [Converted_City | convert_list_to_c(Rest)]; │ │ │ + │ │ │ +convert_list_to_c([City | Rest]) -> │ │ │ + [City | convert_list_to_c(Rest)]; │ │ │ + │ │ │ +convert_list_to_c([]) -> │ │ │ + [].

    Test the function:

    54> c(tut7).
    │ │ │ +{ok, tut7}.
    │ │ │ +55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {cape_town,{c,21.11111111111111}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2.2222222222222223}},
    │ │ │ + {london,{c,2.2222222222222223}}]

    Explanation:

    format_temps(List_of_cities) ->
    │ │ │ +    convert_list_to_c(List_of_cities).

    Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ off the head of the List_of_cities, converts it to Celsius if needed. The | │ │ │ -operator is used to add the (maybe) converted to the converted rest of the list:

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

    This is done until the end of the list is reached, that is, the list is empty:

    convert_list_to_c([]) ->
    │ │ │ -    [].

    Now when the list is converted, a function to print it is added:

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ -
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ -    print_temp(Converted_List).
    │ │ │ -
    │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].
    │ │ │ -
    │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ -    print_temp(Rest);
    │ │ │ -print_temp([]) ->
    │ │ │ -    ok.
    56> c(tut7).
    │ │ │ -{ok,tut7}
    │ │ │ -57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +operator is used to add the (maybe) converted to the converted rest of the list:

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

    This is done until the end of the list is reached, that is, the list is empty:

    convert_list_to_c([]) ->
    │ │ │ +    [].

    Now when the list is converted, a function to print it is added:

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │ +
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ +    print_temp(Converted_List).
    │ │ │ +
    │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ +    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([City | Rest]) ->
    │ │ │ +    [City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([]) ->
    │ │ │ +    [].
    │ │ │ +
    │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ +    print_temp(Rest);
    │ │ │ +print_temp([]) ->
    │ │ │ +    ok.
    56> c(tut7).
    │ │ │ +{ok,tut7}
    │ │ │ +57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  ok

    Now a function has to be added to find the cities with the maximum and minimum │ │ │ temperatures. The following program is not the most efficient way of doing this │ │ │ as you walk through the list of cities four times. But it is better to first │ │ │ strive for clarity and correctness and to make programs efficient only if │ │ │ -needed.

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ +needed.

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ -    print_temp(Converted_List),
    │ │ │ -    {Max_city, Min_city} = find_max_and_min(Converted_List),
    │ │ │ -    print_max_and_min(Max_city, Min_city).
    │ │ │ -
    │ │ │ -convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].
    │ │ │ -
    │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ -    print_temp(Rest);
    │ │ │ -print_temp([]) ->
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ +    print_temp(Converted_List),
    │ │ │ +    {Max_city, Min_city} = find_max_and_min(Converted_List),
    │ │ │ +    print_max_and_min(Max_city, Min_city).
    │ │ │ +
    │ │ │ +convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
    │ │ │ +    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
    │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([City | Rest]) ->
    │ │ │ +    [City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([]) ->
    │ │ │ +    [].
    │ │ │ +
    │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ +    print_temp(Rest);
    │ │ │ +print_temp([]) ->
    │ │ │      ok.
    │ │ │  
    │ │ │ -find_max_and_min([City | Rest]) ->
    │ │ │ -    find_max_and_min(Rest, City, City).
    │ │ │ +find_max_and_min([City | Rest]) ->
    │ │ │ +    find_max_and_min(Rest, City, City).
    │ │ │  
    │ │ │ -find_max_and_min([{Name, {c, Temp}} | Rest],
    │ │ │ -         {Max_Name, {c, Max_Temp}},
    │ │ │ -         {Min_Name, {c, Min_Temp}}) ->
    │ │ │ +find_max_and_min([{Name, {c, Temp}} | Rest],
    │ │ │ +         {Max_Name, {c, Max_Temp}},
    │ │ │ +         {Min_Name, {c, Min_Temp}}) ->
    │ │ │      if
    │ │ │          Temp > Max_Temp ->
    │ │ │ -            Max_City = {Name, {c, Temp}};           % Change
    │ │ │ +            Max_City = {Name, {c, Temp}};           % Change
    │ │ │          true ->
    │ │ │ -            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
    │ │ │ +            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
    │ │ │      end,
    │ │ │      if
    │ │ │           Temp < Min_Temp ->
    │ │ │ -            Min_City = {Name, {c, Temp}};           % Change
    │ │ │ +            Min_City = {Name, {c, Temp}};           % Change
    │ │ │          true ->
    │ │ │ -            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
    │ │ │ +            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
    │ │ │      end,
    │ │ │ -    find_max_and_min(Rest, Max_City, Min_City);
    │ │ │ +    find_max_and_min(Rest, Max_City, Min_City);
    │ │ │  
    │ │ │ -find_max_and_min([], Max_City, Min_City) ->
    │ │ │ -    {Max_City, Min_City}.
    │ │ │ +find_max_and_min([], Max_City, Min_City) ->
    │ │ │ +    {Max_City, Min_City}.
    │ │ │  
    │ │ │ -print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
    │ │ │ -    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
    │ │ │ -    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
    58> c(tut7).
    │ │ │ -{ok, tut7}
    │ │ │ -59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
    │ │ │ +    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
    │ │ │ +    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
    58> c(tut7).
    │ │ │ +{ok, tut7}
    │ │ │ +59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  Max temperature was 21.11111111111111 c in cape_town
    │ │ │  Min temperature was -10 c in moscow
    │ │ │ @@ -773,88 +773,88 @@
    │ │ │          Action 4
    │ │ │  end

    Notice that there is no ; before end. Conditions do the same as guards, that │ │ │ is, tests that succeed or fail. Erlang starts at the top and tests until it │ │ │ finds a condition that succeeds. Then it evaluates (performs) the action │ │ │ following the condition and ignores all other conditions and actions before the │ │ │ end. If no condition matches, a run-time failure occurs. A condition that │ │ │ always succeeds is the atom true. This is often used last in an if, meaning, │ │ │ -do the action following the true if all other conditions have failed.

    The following is a short program to show the workings of if.

    -module(tut9).
    │ │ │ --export([test_if/2]).
    │ │ │ +do the action following the true if all other conditions have failed.

    The following is a short program to show the workings of if.

    -module(tut9).
    │ │ │ +-export([test_if/2]).
    │ │ │  
    │ │ │ -test_if(A, B) ->
    │ │ │ +test_if(A, B) ->
    │ │ │      if
    │ │ │          A == 5 ->
    │ │ │ -            io:format("A == 5~n", []),
    │ │ │ +            io:format("A == 5~n", []),
    │ │ │              a_equals_5;
    │ │ │          B == 6 ->
    │ │ │ -            io:format("B == 6~n", []),
    │ │ │ +            io:format("B == 6~n", []),
    │ │ │              b_equals_6;
    │ │ │          A == 2, B == 3 ->                      %That is A equals 2 and B equals 3
    │ │ │ -            io:format("A == 2, B == 3~n", []),
    │ │ │ +            io:format("A == 2, B == 3~n", []),
    │ │ │              a_equals_2_b_equals_3;
    │ │ │          A == 1 ; B == 7 ->                     %That is A equals 1 or B equals 7
    │ │ │ -            io:format("A == 1 ; B == 7~n", []),
    │ │ │ +            io:format("A == 1 ; B == 7~n", []),
    │ │ │              a_equals_1_or_b_equals_7
    │ │ │ -    end.

    Testing this program gives:

    60> c(tut9).
    │ │ │ -{ok,tut9}
    │ │ │ -61> tut9:test_if(5,33).
    │ │ │ +    end.

    Testing this program gives:

    60> c(tut9).
    │ │ │ +{ok,tut9}
    │ │ │ +61> tut9:test_if(5,33).
    │ │ │  A == 5
    │ │ │  a_equals_5
    │ │ │ -62> tut9:test_if(33,6).
    │ │ │ +62> tut9:test_if(33,6).
    │ │ │  B == 6
    │ │ │  b_equals_6
    │ │ │ -63> tut9:test_if(2, 3).
    │ │ │ +63> tut9:test_if(2, 3).
    │ │ │  A == 2, B == 3
    │ │ │  a_equals_2_b_equals_3
    │ │ │ -64> tut9:test_if(1, 33).
    │ │ │ +64> tut9:test_if(1, 33).
    │ │ │  A == 1 ; B == 7
    │ │ │  a_equals_1_or_b_equals_7
    │ │ │ -65> tut9:test_if(33, 7).
    │ │ │ +65> tut9:test_if(33, 7).
    │ │ │  A == 1 ; B == 7
    │ │ │  a_equals_1_or_b_equals_7
    │ │ │ -66> tut9:test_if(33, 33).
    │ │ │ +66> tut9:test_if(33, 33).
    │ │ │  ** exception error: no true branch found when evaluating an if expression
    │ │ │       in function  tut9:test_if/2 (tut9.erl, line 5)

    Notice that tut9:test_if(33,33) does not cause any condition to succeed. This │ │ │ leads to the run time error if_clause, here nicely formatted by the shell. See │ │ │ Guard Sequences for details of the many guard tests │ │ │ available.

    case is another construct in Erlang. Recall that the convert_length function │ │ │ -was written as:

    convert_length({centimeter, X}) ->
    │ │ │ -    {inch, X / 2.54};
    │ │ │ -convert_length({inch, Y}) ->
    │ │ │ -    {centimeter, Y * 2.54}.

    The same program can also be written as:

    -module(tut10).
    │ │ │ --export([convert_length/1]).
    │ │ │ +was written as:

    convert_length({centimeter, X}) ->
    │ │ │ +    {inch, X / 2.54};
    │ │ │ +convert_length({inch, Y}) ->
    │ │ │ +    {centimeter, Y * 2.54}.

    The same program can also be written as:

    -module(tut10).
    │ │ │ +-export([convert_length/1]).
    │ │ │  
    │ │ │ -convert_length(Length) ->
    │ │ │ +convert_length(Length) ->
    │ │ │      case Length of
    │ │ │ -        {centimeter, X} ->
    │ │ │ -            {inch, X / 2.54};
    │ │ │ -        {inch, Y} ->
    │ │ │ -            {centimeter, Y * 2.54}
    │ │ │ -    end.
    67> c(tut10).
    │ │ │ -{ok,tut10}
    │ │ │ -68> tut10:convert_length({inch, 6}).
    │ │ │ -{centimeter,15.24}
    │ │ │ -69> tut10:convert_length({centimeter, 2.5}).
    │ │ │ -{inch,0.984251968503937}

    Both case and if have return values, that is, in the above example case │ │ │ + {centimeter, X} -> │ │ │ + {inch, X / 2.54}; │ │ │ + {inch, Y} -> │ │ │ + {centimeter, Y * 2.54} │ │ │ + end.

    67> c(tut10).
    │ │ │ +{ok,tut10}
    │ │ │ +68> tut10:convert_length({inch, 6}).
    │ │ │ +{centimeter,15.24}
    │ │ │ +69> tut10:convert_length({centimeter, 2.5}).
    │ │ │ +{inch,0.984251968503937}

    Both case and if have return values, that is, in the above example case │ │ │ returned either {inch,X/2.54} or {centimeter,Y*2.54}. The behaviour of │ │ │ case can also be modified by using guards. The following example clarifies │ │ │ this. It tells us the length of a month, given the year. The year must be known, │ │ │ -since February has 29 days in a leap year.

    -module(tut11).
    │ │ │ --export([month_length/2]).
    │ │ │ +since February has 29 days in a leap year.

    -module(tut11).
    │ │ │ +-export([month_length/2]).
    │ │ │  
    │ │ │ -month_length(Year, Month) ->
    │ │ │ +month_length(Year, Month) ->
    │ │ │      %% All years divisible by 400 are leap
    │ │ │      %% Years divisible by 100 are not leap (except the 400 rule above)
    │ │ │      %% Years divisible by 4 are leap (except the 100 rule above)
    │ │ │      Leap = if
    │ │ │ -        trunc(Year / 400) * 400 == Year ->
    │ │ │ +        trunc(Year / 400) * 400 == Year ->
    │ │ │              leap;
    │ │ │ -        trunc(Year / 100) * 100 == Year ->
    │ │ │ +        trunc(Year / 100) * 100 == Year ->
    │ │ │              not_leap;
    │ │ │ -        trunc(Year / 4) * 4 == Year ->
    │ │ │ +        trunc(Year / 4) * 4 == Year ->
    │ │ │              leap;
    │ │ │          true ->
    │ │ │              not_leap
    │ │ │      end,
    │ │ │      case Month of
    │ │ │          sep -> 30;
    │ │ │          apr -> 30;
    │ │ │ @@ -865,152 +865,152 @@
    │ │ │          jan -> 31;
    │ │ │          mar -> 31;
    │ │ │          may -> 31;
    │ │ │          jul -> 31;
    │ │ │          aug -> 31;
    │ │ │          oct -> 31;
    │ │ │          dec -> 31
    │ │ │ -    end.
    70> c(tut11).
    │ │ │ -{ok,tut11}
    │ │ │ -71> tut11:month_length(2004, feb).
    │ │ │ +    end.
    70> c(tut11).
    │ │ │ +{ok,tut11}
    │ │ │ +71> tut11:month_length(2004, feb).
    │ │ │  29
    │ │ │ -72> tut11:month_length(2003, feb).
    │ │ │ +72> tut11:month_length(2003, feb).
    │ │ │  28
    │ │ │ -73> tut11:month_length(1947, aug).
    │ │ │ +73> tut11:month_length(1947, aug).
    │ │ │  31

    │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │

    │ │ │

    BIFs are functions that for some reason are built-in to the Erlang virtual │ │ │ machine. BIFs often implement functionality that is impossible or is too │ │ │ inefficient to implement in Erlang. Some BIFs can be called using the function │ │ │ name only but they are by default belonging to the erlang module. For example, │ │ │ the call to the BIF trunc below is equivalent to a call to erlang:trunc.

    As shown, first it is checked if a year is leap. If a year is divisible by 400, │ │ │ it is a leap year. To determine this, first divide the year by 400 and use the │ │ │ BIF trunc (more about this later) to cut off any decimals. Then multiply by │ │ │ 400 again and see if the same value is returned again. For example, year 2004:

    2004 / 400 = 5.01
    │ │ │ -trunc(5.01) = 5
    │ │ │ +trunc(5.01) = 5
    │ │ │  5 * 400 = 2000

    2000 is not the same as 2004, so 2004 is not divisible by 400. Year 2000:

    2000 / 400 = 5.0
    │ │ │ -trunc(5.0) = 5
    │ │ │ +trunc(5.0) = 5
    │ │ │  5 * 400 = 2000

    That is, a leap year. The next two trunc-tests evaluate if the year is │ │ │ divisible by 100 or 4 in the same way. The first if returns leap or │ │ │ not_leap, which lands up in the variable Leap. This variable is used in the │ │ │ guard for feb in the following case that tells us how long the month is.

    This example showed the use of trunc. It is easier to use the Erlang operator │ │ │ rem that gives the remainder after division, for example:

    74> 2004 rem 400.
    │ │ │ -4

    So instead of writing:

    trunc(Year / 400) * 400 == Year ->
    │ │ │ +4

    So instead of writing:

    trunc(Year / 400) * 400 == Year ->
    │ │ │      leap;

    it can be written:

    Year rem 400 == 0 ->
    │ │ │      leap;

    There are many other BIFs such as trunc. Only a few BIFs can be used in │ │ │ guards, and you cannot use functions you have defined yourself in guards. (see │ │ │ Guard Sequences) (For advanced readers: This is to │ │ │ ensure that guards do not have side effects.) Let us play with a few of these │ │ │ -functions in the shell:

    75> trunc(5.6).
    │ │ │ +functions in the shell:

    75> trunc(5.6).
    │ │ │  5
    │ │ │ -76> round(5.6).
    │ │ │ +76> round(5.6).
    │ │ │  6
    │ │ │ -77> length([a,b,c,d]).
    │ │ │ +77> length([a,b,c,d]).
    │ │ │  4
    │ │ │ -78> float(5).
    │ │ │ +78> float(5).
    │ │ │  5.0
    │ │ │ -79> is_atom(hello).
    │ │ │ +79> is_atom(hello).
    │ │ │  true
    │ │ │ -80> is_atom("hello").
    │ │ │ +80> is_atom("hello").
    │ │ │  false
    │ │ │ -81> is_tuple({paris, {c, 30}}).
    │ │ │ +81> is_tuple({paris, {c, 30}}).
    │ │ │  true
    │ │ │ -82> is_tuple([paris, {c, 30}]).
    │ │ │ +82> is_tuple([paris, {c, 30}]).
    │ │ │  false

    All of these can be used in guards. Now for some BIFs that cannot be used in │ │ │ -guards:

    83> atom_to_list(hello).
    │ │ │ +guards:

    83> atom_to_list(hello).
    │ │ │  "hello"
    │ │ │ -84> list_to_atom("goodbye").
    │ │ │ +84> list_to_atom("goodbye").
    │ │ │  goodbye
    │ │ │ -85> integer_to_list(22).
    │ │ │ +85> integer_to_list(22).
    │ │ │  "22"

    These three BIFs do conversions that would be difficult (or impossible) to do in │ │ │ Erlang.

    │ │ │ │ │ │ │ │ │ │ │ │ Higher-Order Functions (Funs) │ │ │

    │ │ │

    Erlang, like most modern functional programming languages, has higher-order │ │ │ -functions. Here is an example using the shell:

    86> Xf = fun(X) -> X * 2 end.
    │ │ │ +functions. Here is an example using the shell:

    86> Xf = fun(X) -> X * 2 end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -87> Xf(5).
    │ │ │ +87> Xf(5).
    │ │ │  10

    Here is defined a function that doubles the value of a number and assigned this │ │ │ function to a variable. Thus Xf(5) returns value 10. Two useful functions when │ │ │ -working with lists are foreach and map, which are defined as follows:

    foreach(Fun, [First|Rest]) ->
    │ │ │ -    Fun(First),
    │ │ │ -    foreach(Fun, Rest);
    │ │ │ -foreach(Fun, []) ->
    │ │ │ +working with lists are foreach and map, which are defined as follows:

    foreach(Fun, [First|Rest]) ->
    │ │ │ +    Fun(First),
    │ │ │ +    foreach(Fun, Rest);
    │ │ │ +foreach(Fun, []) ->
    │ │ │      ok.
    │ │ │  
    │ │ │ -map(Fun, [First|Rest]) ->
    │ │ │ -    [Fun(First)|map(Fun,Rest)];
    │ │ │ -map(Fun, []) ->
    │ │ │ -    [].

    These two functions are provided in the standard module lists. foreach takes │ │ │ +map(Fun, [First|Rest]) -> │ │ │ + [Fun(First)|map(Fun,Rest)]; │ │ │ +map(Fun, []) -> │ │ │ + [].

    These two functions are provided in the standard module lists. foreach takes │ │ │ a list and applies a fun to every element in the list. map creates a new list │ │ │ by applying a fun to every element in a list. Going back to the shell, map is │ │ │ -used and a fun to add 3 to every element of a list:

    88> Add_3 = fun(X) -> X + 3 end.
    │ │ │ +used and a fun to add 3 to every element of a list:

    88> Add_3 = fun(X) -> X + 3 end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -89> lists:map(Add_3, [1,2,3]).
    │ │ │ -[4,5,6]

    Let us (again) print the temperatures in a list of cities:

    90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
    │ │ │ -[City, X, Temp]) end.
    │ │ │ +89> lists:map(Add_3, [1,2,3]).
    │ │ │ +[4,5,6]

    Let us (again) print the temperatures in a list of cities:

    90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
    │ │ │ +[City, X, Temp]) end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          c -10
    │ │ │  cape_town       f 70
    │ │ │  stockholm       c -4
    │ │ │  paris           f 28
    │ │ │  london          f 36
    │ │ │  ok

    Let us now define a fun that can be used to go through a list of cities and │ │ │ -temperatures and transform them all to Celsius.

    -module(tut13).
    │ │ │ +temperatures and transform them all to Celsius.

    -module(tut13).
    │ │ │  
    │ │ │ --export([convert_list_to_c/1]).
    │ │ │ +-export([convert_list_to_c/1]).
    │ │ │  
    │ │ │ -convert_to_c({Name, {f, Temp}}) ->
    │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
    │ │ │ -convert_to_c({Name, {c, Temp}}) ->
    │ │ │ -    {Name, {c, Temp}}.
    │ │ │ -
    │ │ │ -convert_list_to_c(List) ->
    │ │ │ -    lists:map(fun convert_to_c/1, List).
    92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {cape_town,{c,21}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2}},
    │ │ │ - {london,{c,2}}]

    The convert_to_c function is the same as before, but here it is used as a fun:

    lists:map(fun convert_to_c/1, List)

    When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ + {Name, {c, Temp}}. │ │ │ + │ │ │ +convert_list_to_c(List) -> │ │ │ + lists:map(fun convert_to_c/1, List).

    92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {cape_town,{c,21}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2}},
    │ │ │ + {london,{c,2}}]

    The convert_to_c function is the same as before, but here it is used as a fun:

    lists:map(fun convert_to_c/1, List)

    When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ Function/Arity (remember that Arity = number of arguments). So in the │ │ │ map-call lists:map(fun convert_to_c/1, List) is written. As shown, │ │ │ convert_list_to_c becomes much shorter and easier to understand.

    The standard module lists also contains a function sort(Fun, List) where │ │ │ Fun is a fun with two arguments. This fun returns true if the first argument │ │ │ is less than the second argument, or else false. Sorting is added to the │ │ │ -convert_list_to_c:

    -module(tut13).
    │ │ │ +convert_list_to_c:

    -module(tut13).
    │ │ │  
    │ │ │ --export([convert_list_to_c/1]).
    │ │ │ +-export([convert_list_to_c/1]).
    │ │ │  
    │ │ │ -convert_to_c({Name, {f, Temp}}) ->
    │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
    │ │ │ -convert_to_c({Name, {c, Temp}}) ->
    │ │ │ -    {Name, {c, Temp}}.
    │ │ │ -
    │ │ │ -convert_list_to_c(List) ->
    │ │ │ -    New_list = lists:map(fun convert_to_c/1, List),
    │ │ │ -    lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->
    │ │ │ -                       Temp1 < Temp2 end, New_list).
    93> c(tut13).
    │ │ │ -{ok,tut13}
    │ │ │ -94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2}},
    │ │ │ - {london,{c,2}},
    │ │ │ - {cape_town,{c,21}}]

    In sort the fun is used:

    fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

    Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ + {Name, {c, Temp}}. │ │ │ + │ │ │ +convert_list_to_c(List) -> │ │ │ + New_list = lists:map(fun convert_to_c/1, List), │ │ │ + lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> │ │ │ + Temp1 < Temp2 end, New_list).

    93> c(tut13).
    │ │ │ +{ok,tut13}
    │ │ │ +94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2}},
    │ │ │ + {london,{c,2}},
    │ │ │ + {cape_town,{c,21}}]

    In sort the fun is used:

    fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

    Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ shorthand for a variable that gets a value, but the value is ignored. This can │ │ │ be used anywhere suitable, not just in funs. Temp1 < Temp2 returns true if │ │ │ Temp1 is less than Temp2.

    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/spec_proc.html │ │ │ @@ -123,72 +123,72 @@ │ │ │ │ │ │ │ │ │ │ │ │ Simple Debugging │ │ │ │ │ │

    The sys module has functions for simple debugging of processes implemented │ │ │ using behaviours. The code_lock example from │ │ │ -gen_statem Behaviour is used to illustrate this:

    Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +gen_statem Behaviour is used to illustrate this:

    Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> code_lock:start_link([1,2,3,4]).
    │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> code_lock:start_link([1,2,3,4]).
    │ │ │  Lock
    │ │ │ -{ok,<0.90.0>}
    │ │ │ -2> sys:statistics(code_lock, true).
    │ │ │ +{ok,<0.90.0>}
    │ │ │ +2> sys:statistics(code_lock, true).
    │ │ │  ok
    │ │ │ -3> sys:trace(code_lock, true).
    │ │ │ +3> sys:trace(code_lock, true).
    │ │ │  ok
    │ │ │ -4> code_lock:button(1).
    │ │ │ -*DBG* code_lock receive cast {button,1} in state locked
    │ │ │ +4> code_lock:button(1).
    │ │ │ +*DBG* code_lock receive cast {button,1} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,1} in state locked
    │ │ │ -5> code_lock:button(2).
    │ │ │ -*DBG* code_lock receive cast {button,2} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,1} in state locked
    │ │ │ +5> code_lock:button(2).
    │ │ │ +*DBG* code_lock receive cast {button,2} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,2} in state locked
    │ │ │ -6> code_lock:button(3).
    │ │ │ -*DBG* code_lock receive cast {button,3} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,2} in state locked
    │ │ │ +6> code_lock:button(3).
    │ │ │ +*DBG* code_lock receive cast {button,3} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,3} in state locked
    │ │ │ -7> code_lock:button(4).
    │ │ │ -*DBG* code_lock receive cast {button,4} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,3} in state locked
    │ │ │ +7> code_lock:button(4).
    │ │ │ +*DBG* code_lock receive cast {button,4} in state locked
    │ │ │  ok
    │ │ │  Unlock
    │ │ │ -*DBG* code_lock consume cast {button,4} in state locked => open
    │ │ │ -*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
    │ │ │ +*DBG* code_lock consume cast {button,4} in state locked => open
    │ │ │ +*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
    │ │ │  *DBG* code_lock receive state_timeout lock in state open
    │ │ │  Lock
    │ │ │  *DBG* code_lock consume state_timeout lock in state open => locked
    │ │ │ -8> sys:statistics(code_lock, get).
    │ │ │ -{ok,[{start_time,{{2024,5,3},{8,11,1}}},
    │ │ │ -     {current_time,{{2024,5,3},{8,11,48}}},
    │ │ │ -     {reductions,4098},
    │ │ │ -     {messages_in,5},
    │ │ │ -     {messages_out,0}]}
    │ │ │ -9> sys:statistics(code_lock, false).
    │ │ │ +8> sys:statistics(code_lock, get).
    │ │ │ +{ok,[{start_time,{{2024,5,3},{8,11,1}}},
    │ │ │ +     {current_time,{{2024,5,3},{8,11,48}}},
    │ │ │ +     {reductions,4098},
    │ │ │ +     {messages_in,5},
    │ │ │ +     {messages_out,0}]}
    │ │ │ +9> sys:statistics(code_lock, false).
    │ │ │  ok
    │ │ │ -10> sys:trace(code_lock, false).
    │ │ │ +10> sys:trace(code_lock, false).
    │ │ │  ok
    │ │ │ -11> sys:get_status(code_lock).
    │ │ │ -{status,<0.90.0>,
    │ │ │ -        {module,gen_statem},
    │ │ │ -        [[{'$initial_call',{code_lock,init,1}},
    │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
    │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
    │ │ │ -         running,<0.88.0>,[],
    │ │ │ -         [{header,"Status for state machine code_lock"},
    │ │ │ -          {data,[{"Status",running},
    │ │ │ -                 {"Parent",<0.88.0>},
    │ │ │ -                 {"Modules",[code_lock]},
    │ │ │ -                 {"Time-outs",{0,[]}},
    │ │ │ -                 {"Logged Events",[]},
    │ │ │ -                 {"Postponed",[]}]},
    │ │ │ -          {data,[{"State",
    │ │ │ -                  {locked,#{code => [1,2,3,4],
    │ │ │ -                            length => 4,buttons => []}}}]}]]}

    │ │ │ +11> sys:get_status(code_lock). │ │ │ +{status,<0.90.0>, │ │ │ + {module,gen_statem}, │ │ │ + [[{'$initial_call',{code_lock,init,1}}, │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ + running,<0.88.0>,[], │ │ │ + [{header,"Status for state machine code_lock"}, │ │ │ + {data,[{"Status",running}, │ │ │ + {"Parent",<0.88.0>}, │ │ │ + {"Modules",[code_lock]}, │ │ │ + {"Time-outs",{0,[]}}, │ │ │ + {"Logged Events",[]}, │ │ │ + {"Postponed",[]}]}, │ │ │ + {data,[{"State", │ │ │ + {locked,#{code => [1,2,3,4], │ │ │ + length => 4,buttons => []}}}]}]]}

    │ │ │ │ │ │ │ │ │ │ │ │ Special Processes │ │ │

    │ │ │

    This section describes how to write a process that complies to the OTP design │ │ │ principles, without using a standard behaviour. Such a process is to:

    System messages are messages with a special meaning, used in the supervision │ │ │ @@ -198,238 +198,238 @@ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │ │ │

    Here follows the simple server from │ │ │ Overview, │ │ │ -implemented using sys and proc_lib to fit into a supervision tree:

    -module(ch4).
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/1]).
    │ │ │ --export([system_continue/3, system_terminate/4,
    │ │ │ +implemented using sys and proc_lib to fit into a supervision tree:

    -module(ch4).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/1]).
    │ │ │ +-export([system_continue/3, system_terminate/4,
    │ │ │           write_debug/3,
    │ │ │ -         system_get_state/1, system_replace_state/2]).
    │ │ │ +         system_get_state/1, system_replace_state/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    proc_lib:start_link(ch4, init, [self()]).
    │ │ │ +start_link() ->
    │ │ │ +    proc_lib:start_link(ch4, init, [self()]).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    ch4 ! {self(), alloc},
    │ │ │ +alloc() ->
    │ │ │ +    ch4 ! {self(), alloc},
    │ │ │      receive
    │ │ │ -        {ch4, Res} ->
    │ │ │ +        {ch4, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    ch4 ! {free, Ch},
    │ │ │ +free(Ch) ->
    │ │ │ +    ch4 ! {free, Ch},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init(Parent) ->
    │ │ │ -    register(ch4, self()),
    │ │ │ -    Chs = channels(),
    │ │ │ -    Deb = sys:debug_options([]),
    │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ -    loop(Chs, Parent, Deb).
    │ │ │ +init(Parent) ->
    │ │ │ +    register(ch4, self()),
    │ │ │ +    Chs = channels(),
    │ │ │ +    Deb = sys:debug_options([]),
    │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ +    loop(Chs, Parent, Deb).
    │ │ │  
    │ │ │ -loop(Chs, Parent, Deb) ->
    │ │ │ +loop(Chs, Parent, Deb) ->
    │ │ │      receive
    │ │ │ -        {From, alloc} ->
    │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {in, alloc, From}),
    │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ -            From ! {ch4, Ch},
    │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
    │ │ │ -            loop(Chs2, Parent, Deb3);
    │ │ │ -        {free, Ch} ->
    │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {in, {free, Ch}}),
    │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ -            loop(Chs2, Parent, Deb2);
    │ │ │ -
    │ │ │ -        {system, From, Request} ->
    │ │ │ -            sys:handle_system_msg(Request, From, Parent,
    │ │ │ -                                  ch4, Deb, Chs)
    │ │ │ +        {From, alloc} ->
    │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {in, alloc, From}),
    │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ +            From ! {ch4, Ch},
    │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
    │ │ │ +            loop(Chs2, Parent, Deb3);
    │ │ │ +        {free, Ch} ->
    │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {in, {free, Ch}}),
    │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ +            loop(Chs2, Parent, Deb2);
    │ │ │ +
    │ │ │ +        {system, From, Request} ->
    │ │ │ +            sys:handle_system_msg(Request, From, Parent,
    │ │ │ +                                  ch4, Deb, Chs)
    │ │ │      end.
    │ │ │  
    │ │ │ -system_continue(Parent, Deb, Chs) ->
    │ │ │ -    loop(Chs, Parent, Deb).
    │ │ │ +system_continue(Parent, Deb, Chs) ->
    │ │ │ +    loop(Chs, Parent, Deb).
    │ │ │  
    │ │ │ -system_terminate(Reason, _Parent, _Deb, _Chs) ->
    │ │ │ -    exit(Reason).
    │ │ │ +system_terminate(Reason, _Parent, _Deb, _Chs) ->
    │ │ │ +    exit(Reason).
    │ │ │  
    │ │ │ -system_get_state(Chs) ->
    │ │ │ -    {ok, Chs}.
    │ │ │ +system_get_state(Chs) ->
    │ │ │ +    {ok, Chs}.
    │ │ │  
    │ │ │ -system_replace_state(StateFun, Chs) ->
    │ │ │ -    NChs = StateFun(Chs),
    │ │ │ -    {ok, NChs, NChs}.
    │ │ │ +system_replace_state(StateFun, Chs) ->
    │ │ │ +    NChs = StateFun(Chs),
    │ │ │ +    {ok, NChs, NChs}.
    │ │ │  
    │ │ │ -write_debug(Dev, Event, Name) ->
    │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

    As it is not relevant to the example, the channel handling functions have been │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

    As it is not relevant to the example, the channel handling functions have been │ │ │ omitted. To compile this example, the │ │ │ implementation of channel handling │ │ │ needs to be added to the module.

    Here is an example showing how the debugging functions in the sys │ │ │ module can be used for ch4:

    % erl
    │ │ │ -Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> ch4:start_link().
    │ │ │ -{ok,<0.90.0>}
    │ │ │ -2> sys:statistics(ch4, true).
    │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> ch4:start_link().
    │ │ │ +{ok,<0.90.0>}
    │ │ │ +2> sys:statistics(ch4, true).
    │ │ │  ok
    │ │ │ -3> sys:trace(ch4, true).
    │ │ │ +3> sys:trace(ch4, true).
    │ │ │  ok
    │ │ │ -4> ch4:alloc().
    │ │ │ -ch4 event = {in,alloc,<0.88.0>}
    │ │ │ -ch4 event = {out,{ch4,1},<0.88.0>}
    │ │ │ +4> ch4:alloc().
    │ │ │ +ch4 event = {in,alloc,<0.88.0>}
    │ │ │ +ch4 event = {out,{ch4,1},<0.88.0>}
    │ │ │  1
    │ │ │ -5> ch4:free(ch1).
    │ │ │ -ch4 event = {in,{free,ch1}}
    │ │ │ +5> ch4:free(ch1).
    │ │ │ +ch4 event = {in,{free,ch1}}
    │ │ │  ok
    │ │ │ -6> sys:statistics(ch4, get).
    │ │ │ -{ok,[{start_time,{{2024,5,3},{8,26,13}}},
    │ │ │ -     {current_time,{{2024,5,3},{8,26,49}}},
    │ │ │ -     {reductions,202},
    │ │ │ -     {messages_in,2},
    │ │ │ -     {messages_out,1}]}
    │ │ │ -7> sys:statistics(ch4, false).
    │ │ │ +6> sys:statistics(ch4, get).
    │ │ │ +{ok,[{start_time,{{2024,5,3},{8,26,13}}},
    │ │ │ +     {current_time,{{2024,5,3},{8,26,49}}},
    │ │ │ +     {reductions,202},
    │ │ │ +     {messages_in,2},
    │ │ │ +     {messages_out,1}]}
    │ │ │ +7> sys:statistics(ch4, false).
    │ │ │  ok
    │ │ │ -8> sys:trace(ch4, false).
    │ │ │ +8> sys:trace(ch4, false).
    │ │ │  ok
    │ │ │ -9> sys:get_status(ch4).
    │ │ │ -{status,<0.90.0>,
    │ │ │ -        {module,ch4},
    │ │ │ -        [[{'$initial_call',{ch4,init,1}},
    │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
    │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
    │ │ │ -         running,<0.88.0>,[],
    │ │ │ -         {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

    │ │ │ +9> sys:get_status(ch4). │ │ │ +{status,<0.90.0>, │ │ │ + {module,ch4}, │ │ │ + [[{'$initial_call',{ch4,init,1}}, │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ + running,<0.88.0>,[], │ │ │ + {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

    │ │ │ │ │ │ │ │ │ │ │ │ Starting the Process │ │ │

    │ │ │

    A function in the proc_lib module is to be used to start the process. Several │ │ │ functions are available, for example, │ │ │ proc_lib:spawn_link/3,4 │ │ │ for asynchronous start and │ │ │ proc_lib:start_link/3,4,5 for synchronous start.

    Information necessary for a process within a supervision tree, such as │ │ │ details on ancestors and the initial call, is stored when a process │ │ │ is started through one of these functions.

    If the process terminates with a reason other than normal or shutdown, a │ │ │ crash report is generated. For more information about the crash report, see │ │ │ Logging in Kernel User's Guide.

    In the example, synchronous start is used. The process starts by calling │ │ │ -ch4:start_link():

    start_link() ->
    │ │ │ -    proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ +ch4:start_link():

    start_link() ->
    │ │ │ +    proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ name, a function name, and an argument list as arguments. It then │ │ │ spawns a new process and establishes a link. The new process starts │ │ │ by executing the given function, here ch4:init(Pid), where Pid is │ │ │ the pid of the parent process (obtained by the call to │ │ │ self() in the call to proc_lib:start_link/3).

    All initialization, including name registration, is done in init/1. The new │ │ │ -process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │ +process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ -    loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ + proc_lib:init_ack(Parent, {ok, self()}), │ │ │ + loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ proc_lib:init_ack/1,2 or │ │ │ proc_lib:init_fail/2,3 has been called, │ │ │ or the process has exited.

    │ │ │ │ │ │ │ │ │ │ │ │ Debugging │ │ │

    │ │ │

    To support the debug facilities in sys, a debug structure is needed. The │ │ │ -Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │ +Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    Deb = sys:debug_options([]),
    │ │ │ +    Deb = sys:debug_options([]),
    │ │ │      ...
    │ │ │ -    loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ + loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ example means that debugging is initially disabled. For information about the │ │ │ possible options, see sys in STDLIB.

    For each system event to be logged or traced, the following function │ │ │ -is to be called:

    sys:handle_debug(Deb, Func, Info, Event) => Deb1

    The arguments have the follow meaning:

    • Deb is the debug structure as returned from sys:debug_options/1.
    • Func is a fun specifying a (user-defined) function used to format trace │ │ │ +is to be called:

      sys:handle_debug(Deb, Func, Info, Event) => Deb1

      The arguments have the follow meaning:

      • Deb is the debug structure as returned from sys:debug_options/1.
      • Func is a fun specifying a (user-defined) function used to format trace │ │ │ output. For each system event, the format function is called as │ │ │ Func(Dev, Event, Info), where:
        • Dev is the I/O device to which the output is to be printed. See io │ │ │ in STDLIB.
        • Event and Info are passed as-is from the call to sys:handle_debug/4.
      • Info is used to pass more information to Func. It can be any term, and it │ │ │ is passed as-is.
      • Event is the system event. It is up to the user to define what a system │ │ │ event is and how it is to be represented. Typically, at least incoming and │ │ │ outgoing messages are considered system events and represented by the tuples │ │ │ {in,Msg[,From]} and {out,Msg,To[,State]}, respectively.

      sys:handle_debug/4 returns an updated debug structure Deb1.

      In the example, sys:handle_debug/4 is called for each incoming and │ │ │ outgoing message. The format function Func is the function │ │ │ -ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │ +ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │      receive
      │ │ │ -        {From, alloc} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, alloc, From}),
      │ │ │ -            {Ch, Chs2} = alloc(Chs),
      │ │ │ -            From ! {ch4, Ch},
      │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ -            loop(Chs2, Parent, Deb3);
      │ │ │ -        {free, Ch} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, {free, Ch}}),
      │ │ │ -            Chs2 = free(Ch, Chs),
      │ │ │ -            loop(Chs2, Parent, Deb2);
      │ │ │ +        {From, alloc} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, alloc, From}),
      │ │ │ +            {Ch, Chs2} = alloc(Chs),
      │ │ │ +            From ! {ch4, Ch},
      │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ +            loop(Chs2, Parent, Deb3);
      │ │ │ +        {free, Ch} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, {free, Ch}}),
      │ │ │ +            Chs2 = free(Ch, Chs),
      │ │ │ +            loop(Chs2, Parent, Deb2);
      │ │ │          ...
      │ │ │      end.
      │ │ │  
      │ │ │ -write_debug(Dev, Event, Name) ->
      │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ │ │ │ │ │ │ │ │ │ Handling System Messages │ │ │

      │ │ │

      System messages are received as:

      {system, From, Request}

      The content and meaning of these messages are not to be interpreted by the │ │ │ -process. Instead the following function is to be called:

      sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

      The arguments have the following meaning:

      • Request and From from the received system message are to be │ │ │ +process. Instead the following function is to be called:

        sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

        The arguments have the following meaning:

        • Request and From from the received system message are to be │ │ │ passed as-is to the call to sys:handle_system_msg/6.
        • Parent is the pid of the parent process.
        • Module is the name of the module implementing the speciall process.
        • Deb is the debug structure.
        • State is a term describing the internal state and is passed on to │ │ │ Module:system_continue/3, Module:system_terminate/4/ │ │ │ Module:system_get_state/1, and Module:system_replace_state/2.

        sys:handle_system_msg/6 does not return. It handles the system │ │ │ message and eventually calls either of the following functions:

        • Module:system_continue(Parent, Deb, State) - if process execution is to │ │ │ continue.

        • Module:system_terminate(Reason, Parent, Deb, State) - if the │ │ │ process is to terminate.

        While handling the system message, sys:handle_system_msg/6 can call │ │ │ one of the following functions:

        • Module:system_get_state(State) - if the process is to return its state.

        • Module:system_replace_state(StateFun, State) - if the process is │ │ │ to replace its state using the fun StateFun fun. See sys:replace_state/3 │ │ │ for more information.

        • system_code_change(Misc, Module, OldVsn, Extra) - if the process is to │ │ │ perform a code change.

        A process in a supervision tree is expected to terminate with the same reason as │ │ │ -its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │ +its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │  
        │ │ │ -        {system, From, Request} ->
        │ │ │ -            sys:handle_system_msg(Request, From, Parent,
        │ │ │ -                                  ch4, Deb, Chs)
        │ │ │ +        {system, From, Request} ->
        │ │ │ +            sys:handle_system_msg(Request, From, Parent,
        │ │ │ +                                  ch4, Deb, Chs)
        │ │ │      end.
        │ │ │  
        │ │ │ -system_continue(Parent, Deb, Chs) ->
        │ │ │ -    loop(Chs, Parent, Deb).
        │ │ │ +system_continue(Parent, Deb, Chs) ->
        │ │ │ +    loop(Chs, Parent, Deb).
        │ │ │  
        │ │ │ -system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ -    exit(Reason).
        │ │ │ +system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ +    exit(Reason).
        │ │ │  
        │ │ │ -system_get_state(Chs) ->
        │ │ │ -    {ok, Chs, Chs}.
        │ │ │ +system_get_state(Chs) ->
        │ │ │ +    {ok, Chs, Chs}.
        │ │ │  
        │ │ │ -system_replace_state(StateFun, Chs) ->
        │ │ │ -    NChs = StateFun(Chs),
        │ │ │ -    {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ +system_replace_state(StateFun, Chs) -> │ │ │ + NChs = StateFun(Chs), │ │ │ + {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ of 'EXIT' messages from its parent process and terminate using the │ │ │ -same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │ +same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │      ...,
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │      ...,
        │ │ │ -    loop(Parent).
        │ │ │ +    loop(Parent).
        │ │ │  
        │ │ │ -loop(Parent) ->
        │ │ │ +loop(Parent) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │ -        {'EXIT', Parent, Reason} ->
        │ │ │ +        {'EXIT', Parent, Reason} ->
        │ │ │              %% Clean up here, if needed.
        │ │ │ -            exit(Reason);
        │ │ │ +            exit(Reason);
        │ │ │          ...
        │ │ │      end.

        │ │ │ │ │ │ │ │ │ │ │ │ User-Defined Behaviours │ │ │

        │ │ │ @@ -448,71 +448,71 @@ │ │ │ function. Note that the -optional_callbacks attribute is to be used together │ │ │ with the -callback attribute; it cannot be combined with the │ │ │ behaviour_info() function described below.

        Tools that need to know about optional callback functions can call │ │ │ Behaviour:behaviour_info(optional_callbacks) to get a list of all optional │ │ │ callback functions.

        Note

        We recommend using the -callback attribute rather than the │ │ │ behaviour_info() function. The reason is that the extra type information can │ │ │ be used by tools to produce documentation or find discrepancies.

        As an alternative to the -callback and -optional_callbacks attributes you │ │ │ -may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ -    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ +may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ +    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ This function is otherwise automatically generated by the compiler using the │ │ │ -callback attributes.

        When the compiler encounters the module attribute -behaviour(Behaviour). in a │ │ │ module Mod, it calls Behaviour:behaviour_info(callbacks) and compares the │ │ │ result with the set of functions actually exported from Mod, and issues a │ │ │ warning if any callback function is missing.

        Example:

        %% User-defined behaviour module
        │ │ │ --module(simple_server).
        │ │ │ --export([start_link/2, init/3, ...]).
        │ │ │ +-module(simple_server).
        │ │ │ +-export([start_link/2, init/3, ...]).
        │ │ │  
        │ │ │ --callback init(State :: term()) -> 'ok'.
        │ │ │ --callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ --callback terminate() -> 'ok'.
        │ │ │ --callback format_state(State :: term()) -> term().
        │ │ │ +-callback init(State :: term()) -> 'ok'.
        │ │ │ +-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ +-callback terminate() -> 'ok'.
        │ │ │ +-callback format_state(State :: term()) -> term().
        │ │ │  
        │ │ │ --optional_callbacks([format_state/1]).
        │ │ │ +-optional_callbacks([format_state/1]).
        │ │ │  
        │ │ │  %% Alternatively you may define:
        │ │ │  %%
        │ │ │  %% -export([behaviour_info/1]).
        │ │ │  %% behaviour_info(callbacks) ->
        │ │ │  %%     [{init,1},
        │ │ │  %%      {handle_req,2},
        │ │ │  %%      {terminate,0}].
        │ │ │  
        │ │ │ -start_link(Name, Module) ->
        │ │ │ -    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │ +start_link(Name, Module) ->
        │ │ │ +    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │  
        │ │ │ -init(Parent, Name, Module) ->
        │ │ │ -    register(Name, self()),
        │ │ │ +init(Parent, Name, Module) ->
        │ │ │ +    register(Name, self()),
        │ │ │      ...,
        │ │ │ -    Dbg = sys:debug_options([]),
        │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ -    loop(Parent, Module, Deb, ...).
        │ │ │ +    Dbg = sys:debug_options([]),
        │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ +    loop(Parent, Module, Deb, ...).
        │ │ │  
        │ │ │ -...

        In a callback module:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +...

        In a callback module:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │  ...

        The contracts specified with -callback attributes in behaviour modules can be │ │ │ further refined by adding -spec attributes in callback modules. This can be │ │ │ useful as -callback contracts are usually generic. The same callback module │ │ │ -with contracts for the callbacks:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +with contracts for the callbacks:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │ --record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │ +-record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │  
        │ │ │ --type state()   :: #state{}.
        │ │ │ --type request() :: {'store', term(), term()};
        │ │ │ -                   {'lookup', term()}.
        │ │ │ +-type state()   :: #state{}.
        │ │ │ +-type request() :: {'store', term(), term()};
        │ │ │ +                   {'lookup', term()}.
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ --spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │ +-spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │  
        │ │ │  ...

        Each -spec contract is to be a subtype of the respective -callback contract.

        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/statem.html │ │ │ @@ -124,15 +124,15 @@ │ │ │ │ │ │

        Established Automata Theory does not deal much with how a state transition │ │ │ is triggered, but assumes that the output is a function of the input │ │ │ (and the state) and that they are some kind of values.

        For an Event-Driven State Machine, the input is an event that triggers │ │ │ a state transition and the output is actions executed during │ │ │ the state transition. Analogously to the mathematical model │ │ │ of a Finite State Machine, it can be described as a set of relations │ │ │ -of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ +of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ and event E occurs, we are to perform actions A, and make a transition │ │ │ to state S'. Notice that S' can be equal to S, │ │ │ and that A can be empty.

        In gen_statem we define a state change as a state transition in which the │ │ │ new state S' is different from the current state S, where "different" means │ │ │ Erlang's strict inequality: =/= also known as "does not match". gen_statem │ │ │ does more things during state changes than during other state transitions.

        As A and S' depend only on S and E, the kind of state machine described │ │ │ here is a Mealy machine (see, for example, the Wikipedia article │ │ │ @@ -405,20 +405,20 @@ │ │ │ │ │ │ State Enter Calls │ │ │ │ │ │

        The gen_statem behaviour can, if this is enabled, regardless of callback │ │ │ mode, automatically call the state callback │ │ │ with special arguments whenever the state changes, so you can write │ │ │ state enter actions near the rest of the state transition rules. │ │ │ -It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │ +It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │      ... code for state enter actions here ...
        │ │ │ -    {keep_state, NewData};
        │ │ │ -StateName(EventType, EventContent, Data) ->
        │ │ │ +    {keep_state, NewData};
        │ │ │ +StateName(EventType, EventContent, Data) ->
        │ │ │      ... code for actions here ...
        │ │ │ -    {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ + {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ allowed return value and state transition actions. │ │ │ You must not change the state, postpone this non-event, │ │ │ insert any events, or change the │ │ │ callback module.

        The first state that is entered after gen_statem:init/1 will get │ │ │ a state enter call with OldState equal to the current state.

        You may repeat the state enter call using the {repeat_state,...} return │ │ │ value from the state callback. In this case │ │ │ OldState will also be equal to the current state.

        Depending on how your state machine is specified, this can be a very useful │ │ │ @@ -499,72 +499,72 @@ │ │ │ │ │ │ locked --> check_code : {button, Button}\n* Collect Buttons │ │ │ check_code --> locked : Incorrect code │ │ │ check_code --> open : Correct code\n* do_unlock()\n* Clear Buttons\n* Set state_timeout 10 s │ │ │ │ │ │ open --> open : {button, Digit} │ │ │ open --> locked : state_timeout\n* do_lock()

        This code lock state machine can be implemented using gen_statem with │ │ │ -the following callback module:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock).
        │ │ │ +the following callback module:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock).
        │ │ │  
        │ │ │ --export([start_link/1]).
        │ │ │ --export([button/1]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([locked/3,open/3]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ -
        │ │ │ -button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ -
        │ │ │ -init(Code) ->
        │ │ │ -    do_lock(),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ -
        │ │ │ -callback_mode() ->
        │ │ │ -    state_functions.
        locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +-export([start_link/1]).
        │ │ │ +-export([button/1]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([locked/3,open/3]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ +
        │ │ │ +button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ +
        │ │ │ +init(Code) ->
        │ │ │ +    do_lock(),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.
        │ │ │ +
        │ │ │ +callback_mode() ->
        │ │ │ +    state_functions.
        locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ -    end.
        open(state_timeout, lock,  Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {next_state, open, Data}.
        do_lock() ->
        │ │ │ -    io:format("Lock~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Unlock~n", []).
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ +    end.
        open(state_timeout, lock,  Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {next_state, open, Data}.
        do_lock() ->
        │ │ │ +    io:format("Lock~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Unlock~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        The code is explained in the next sections.

        │ │ │ │ │ │ │ │ │ │ │ │ Starting gen_statem │ │ │

        │ │ │

        In the example in the previous section, gen_statem is started by calling │ │ │ -code_lock:start_link(Code):

        start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

        start_link/1 calls function gen_statem:start_link/4, │ │ │ +code_lock:start_link(Code):

        start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

        start_link/1 calls function gen_statem:start_link/4, │ │ │ which spawns and links to a new process, a gen_statem.

        • The first argument, {local,?NAME}, specifies the name. In this case, the │ │ │ gen_statem is locally registered as code_lock through the macro ?NAME.

          If the name is omitted, the gen_statem is not registered. Instead its pid │ │ │ must be used. The name can also be specified as {global, Name}, then the │ │ │ gen_statem is registered using global:register_name/2 in Kernel.

        • The second argument, ?MODULE, is the name of the callback module, │ │ │ that is, the module where the callback functions are located, │ │ │ which is this module.

          The interface functions (start_link/1 and button/1) are located in the │ │ │ same module as the callback functions (init/1, locked/3, and open/3). │ │ │ @@ -574,184 +574,184 @@ │ │ │ see gen_statem:start_link/3.

        If name registration succeeds, the new gen_statem process calls callback │ │ │ function code_lock:init(Code). This function is expected to return │ │ │ {ok, State, Data}, where State is the initial state of the gen_statem, │ │ │ in this case locked; assuming that the door is locked to begin with. │ │ │ Data is the internal server data of the gen_statem. Here the server data │ │ │ is a map() with key code that stores the correct │ │ │ button sequence, key length store its length, and key buttons │ │ │ -that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ -    do_lock(),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ +that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ +    do_lock(),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ is synchronous. It does not return until the gen_statem is initialized │ │ │ and is ready to receive events.

        Function gen_statem:start_link/3,4 │ │ │ must be used if the gen_statem is part of a supervision tree, that is, │ │ │ started by a supervisor. Function, │ │ │ gen_statem:start/3,4 can be used to start │ │ │ a standalone gen_statem, meaning it is not part of a supervision tree.

        Function Module:callback_mode/0 selects │ │ │ the CallbackMode for the callback module, │ │ │ in this case state_functions. │ │ │ -That is, each state has its own handler function:

        callback_mode() ->
        │ │ │ +That is, each state has its own handler function:

        callback_mode() ->
        │ │ │      state_functions.

        │ │ │ │ │ │ │ │ │ │ │ │ Handling Events │ │ │

        │ │ │

        The function notifying the code lock about a button event is implemented using │ │ │ -gen_statem:cast/2:

        button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ +gen_statem:cast/2:

        button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ the name used to start it. So, we use the same macro ?NAME as when starting. │ │ │ {button, Button} is the event content.

        The event is sent to the gen_statem. When the event is received, the │ │ │ gen_statem calls StateName(cast, Event, Data), which is expected │ │ │ to return a tuple {next_state, NewStateName, NewData}, or │ │ │ {next_state, NewStateName, NewData, Actions}. StateName is the name │ │ │ of the current state and NewStateName is the name of the next state. │ │ │ NewData is a new value for the server data of the gen_statem, │ │ │ -and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │      end.

        In state locked, when a button is pressed, it is collected with the │ │ │ previously pressed buttons up to the length of the correct code, then │ │ │ compared with the correct code. Depending on the result, the door is │ │ │ either unlocked and the gen_statem goes to state open, or the door │ │ │ remains in state locked.

        When changing to state open, the collected buttons are reset, the lock │ │ │ -unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ -    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ +unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ +    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ This can also be done by returning {keep_state, Data}, or in this case │ │ │ since Data is unchanged, by returning keep_state_and_data.

        │ │ │ │ │ │ │ │ │ │ │ │ State Time-Outs │ │ │

        │ │ │

        When a correct code has been given, the door is unlocked and the following │ │ │ -tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ - [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ +tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ + [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ a time-out occurs. Then, StateName(state_timeout, lock, Data) is called. │ │ │ The time-out occurs when the door has been in state open for 10 seconds. │ │ │ -After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ +After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ the state machine does a state change.

        You can restart, cancel, or update a state time-out. See section │ │ │ Time-Outs for details.

        │ │ │ │ │ │ │ │ │ │ │ │ All State Events │ │ │

        │ │ │

        Sometimes events can arrive in any state of the gen_statem. It is convenient │ │ │ to handle these in a common state handler function that all state functions │ │ │ call for events not specific to the state.

        Consider a code_length/0 function that returns the length │ │ │ of the correct code. We dispatch all events that are not state-specific │ │ │ to the common function handle_common/3:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ -locked(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ +locked(...) -> ... ;
        │ │ │ +locked(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ -open(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ -
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +open(...) -> ... ;
        │ │ │ +open(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │ +
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ +locked(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ +open(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.

        This example uses gen_statem:call/2, which waits for a reply from the server. │ │ │ The reply is sent with a {reply, From, Reply} tuple in an action list in the │ │ │ {keep_state, ...} tuple that retains the current state. This return form is │ │ │ convenient when you want to stay in the current state but do not know or care │ │ │ about what it is.

        If the common state callback needs to know the current state a function │ │ │ -handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ +handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ │ │ │ │ │ │ │ │ │ One State Callback │ │ │

        │ │ │

        If callback mode handle_event_function is used, │ │ │ all events are handled in │ │ │ Module:handle_event/4 and we can │ │ │ (but do not have to) use an event-centered approach where we first branch │ │ │ depending on event and then depending on state:

        ...
        │ │ │ --export([handle_event/4]).
        │ │ │ +-export([handle_event/4]).
        │ │ │  
        │ │ │  ...
        │ │ │ -callback_mode() ->
        │ │ │ +callback_mode() ->
        │ │ │      handle_event_function.
        │ │ │  
        │ │ │ -handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │ +handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │      case State of
        │ │ │  	locked ->
        │ │ │ -            #{length := Length, buttons := Buttons} = Data,
        │ │ │ +            #{length := Length, buttons := Buttons} = Data,
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -                    {next_state, open, Data#{buttons := []},
        │ │ │ -                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +                    do_unlock(),
        │ │ │ +                    {next_state, open, Data#{buttons := []},
        │ │ │ +                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │ +                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │              end;
        │ │ │  	open ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...

        │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

        │ │ │ @@ -763,59 +763,59 @@ │ │ │ │ │ │

        If the gen_statem is part of a supervision tree, no stop function is needed. │ │ │ The gen_statem is automatically terminated by its supervisor. Exactly how │ │ │ this is done is defined by a shutdown strategy │ │ │ set in the supervisor.

        If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_statem must in function init/1 │ │ │ set itself to trap exit signals by calling │ │ │ -process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    do_lock(),
        │ │ │ +process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    do_lock(),
        │ │ │      ...

        When ordered to shut down, the gen_statem then calls callback function │ │ │ terminate(shutdown, State, Data).

        In this example, function terminate/3 locks the door if it is open, │ │ │ so we do not accidentally leave the door open │ │ │ -when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Standalone gen_statem │ │ │

        │ │ │

        If the gen_statem is not part of a supervision tree, it can be stopped │ │ │ using gen_statem:stop/1, preferably through │ │ │ an API function:

        ...
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │  
        │ │ │  ...
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ +stop() -> │ │ │ + gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ for a supervised server and waits for the process to terminate.

        │ │ │ │ │ │ │ │ │ │ │ │ Event Time-Outs │ │ │

        │ │ │

        A time-out feature inherited from gen_statem's predecessor gen_fsm, │ │ │ is an event time-out, that is, if an event arrives the timer is canceled. │ │ │ You get either an event or a time-out, but not both.

        It is ordered by the │ │ │ transition action {timeout, Time, EventContent}, │ │ │ or just an integer Time, even without the enclosing actions list (the latter │ │ │ is a form inherited from gen_fsm).

        This type of time-out is useful, for example, to act on inactivity. │ │ │ Let's restart the code sequence if no button is pressed for say 30 seconds:

        ...
        │ │ │  
        │ │ │ -locked(timeout, _, Data) ->
        │ │ │ -    {next_state, locked, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(timeout, _, Data) ->
        │ │ │ +    {next_state, locked, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ -             30_000} % Time in milliseconds
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ +             30_000} % Time in milliseconds
        │ │ │  ...

        Whenever we receive a button event we start an event time-out of 30 seconds, │ │ │ and if we get an event type of timeout we reset the remaining │ │ │ code sequence.

        An event time-out is canceled by any other event so you either get │ │ │ some other event or the time-out event. Therefore, canceling, │ │ │ restarting, or updating an event time-out is neither possible nor │ │ │ necessary. Whatever event you act on has already canceled │ │ │ the event time-out, so there is never a running event time-out │ │ │ @@ -834,30 +834,30 @@ │ │ │ another, maybe cancel the time-out without changing states, or perhaps run │ │ │ multiple time-outs in parallel. All this can be accomplished with │ │ │ generic time-outs. They may look a little │ │ │ bit like event time-outs but contain │ │ │ a name to allow for any number of them simultaneously and they are │ │ │ not automatically canceled.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using a generic time-out named for example open:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │  ...
        │ │ │  
        │ │ │ -open({timeout,open}, lock, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,Data};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open({timeout,open}, lock, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,Data};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Specific generic time-outs can just as state time-outs │ │ │ be restarted or canceled by setting it to a new time or infinity.

        In this particular case we do not need to cancel the time-out since │ │ │ the time-out event is the only possible reason to do a state change │ │ │ from open to locked.

        Instead of bothering with when to cancel a time-out, a late time-out event │ │ │ can be handled by ignoring it if it arrives in a state │ │ │ where it is known to be late.

        You can restart, cancel, or update a generic time-out. │ │ │ See section Time-Outs for details.

        │ │ │ @@ -869,32 +869,32 @@ │ │ │

        The most versatile way to handle time-outs is to use Erlang Timers; see │ │ │ erlang:start_timer/3,4. Most time-out tasks │ │ │ can be performed with the time-out features in gen_statem, │ │ │ but an example of one that cannot is if you should need the return value │ │ │ from erlang:cancel_timer(Tref), that is, │ │ │ the remaining time of the timer.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using an Erlang Timer:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ +	    do_unlock(),
        │ │ │  	    Tref =
        │ │ │ -                 erlang:start_timer(
        │ │ │ -                     10_000, self(), lock), % Time in milliseconds
        │ │ │ -            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │ +                 erlang:start_timer(
        │ │ │ +                     10_000, self(), lock), % Time in milliseconds
        │ │ │ +            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Removing the timer key from the map when we do a state change to locked │ │ │ is not strictly necessary since we can only get into state open │ │ │ with an updated timer map value. But it can be nice to not have │ │ │ outdated values in the state Data.

        If you need to cancel a timer because of some other event, you can use │ │ │ erlang:cancel_timer(Tref). Note that no time-out │ │ │ message will arrive after this (because the timer has been │ │ │ explicitly canceled), unless you have already postponed one earlier │ │ │ @@ -910,16 +910,16 @@ │ │ │ Postponing Events │ │ │

        │ │ │

        If you want to ignore a particular event in the current state and handle it │ │ │ in a future state, you can postpone the event. A postponed event │ │ │ is retried after a state change, that is, OldState =/= NewState.

        Postponing is ordered by the │ │ │ transition action postpone.

        In this example, instead of ignoring button events while in the open state, │ │ │ we can postpone them handle them later in the locked state:

        ...
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        Since a postponed event is only retried after a state change, you have to │ │ │ think about where to keep a state data item. You can keep it in the server │ │ │ Data or in the State itself, for example by having two more or less │ │ │ identical states to keep a boolean value, or by using a complex state (see │ │ │ section Complex State) with │ │ │ callback mode │ │ │ handle_event_function. If a change │ │ │ @@ -940,55 +940,55 @@ │ │ │ │ │ │ │ │ │ │ │ │ Selective Receive │ │ │ │ │ │

        Erlang's selective receive statement is often used to describe simple state │ │ │ machine examples in straightforward Erlang code. The following is a possible │ │ │ -implementation of the first example:

        -module(code_lock).
        │ │ │ --define(NAME, code_lock_1).
        │ │ │ --export([start_link/1,button/1]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    spawn(
        │ │ │ -      fun () ->
        │ │ │ -	      true = register(?NAME, self()),
        │ │ │ -	      do_lock(),
        │ │ │ -	      locked(Code, length(Code), [])
        │ │ │ -      end).
        │ │ │ +implementation of the first example:

        -module(code_lock).
        │ │ │ +-define(NAME, code_lock_1).
        │ │ │ +-export([start_link/1,button/1]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    spawn(
        │ │ │ +      fun () ->
        │ │ │ +	      true = register(?NAME, self()),
        │ │ │ +	      do_lock(),
        │ │ │ +	      locked(Code, length(Code), [])
        │ │ │ +      end).
        │ │ │  
        │ │ │ -button(Button) ->
        │ │ │ -    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │ +button(Button) ->
        │ │ │ +    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │      receive
        │ │ │ -        {button,Button} ->
        │ │ │ +        {button,Button} ->
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -		    open(Code, Length);
        │ │ │ +                    do_unlock(),
        │ │ │ +		    open(Code, Length);
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    locked(Code, Length, NewButtons)
        │ │ │ +                    locked(Code, Length, NewButtons)
        │ │ │              end
        │ │ │ -    end.
        open(Code, Length) ->
        │ │ │ +    end.
        open(Code, Length) ->
        │ │ │      receive
        │ │ │      after 10_000 -> % Time in milliseconds
        │ │ │ -	    do_lock(),
        │ │ │ -	    locked(Code, Length, [])
        │ │ │ +	    do_lock(),
        │ │ │ +	    locked(Code, Length, [])
        │ │ │      end.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ +do_lock() -> │ │ │ + io:format("Locked~n", []). │ │ │ +do_unlock() -> │ │ │ + io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ events to the locked state.

        A catch-all receive should never be used from a gen_statem behaviour │ │ │ (or from any gen_* behaviour), as the receive statement is within │ │ │ the gen_* engine itself. sys-compatible behaviours must respond to │ │ │ system messages and therefore do that in their engine receive loop, │ │ │ passing non-system messages to the callback module. Using a catch-all │ │ │ receive can result in system messages being discarded, which in turn │ │ │ can lead to unexpected behaviour. If a selective receive must be used, │ │ │ @@ -1011,40 +1011,40 @@ │ │ │ section), especially if only one or a few states have state enter actions, │ │ │ this is a perfect use case for the built in │ │ │ state enter calls.

        You return a list containing state_enter from your │ │ │ callback_mode/0 function and the │ │ │ gen_statem engine will call your state callback once with an event │ │ │ (enter, OldState, ...) whenever it does a state change. Then you │ │ │ just need to handle these event-like calls in all states.

        ...
        │ │ │ -init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length = length(Code)},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ -
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ -
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length = length(Code)},
        │ │ │ +    {ok, locked, Data}.
        │ │ │ +
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │ +
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ +open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │  ...

        You can repeat the state enter code by returning one of │ │ │ {repeat_state, ...},{repeat_state_and_data, _}, │ │ │ or repeat_state_and_data that otherwise behaves exactly like their │ │ │ keep_state siblings. See the type │ │ │ state_callback_result() │ │ │ in the Reference Manual.

        │ │ │ │ │ │ @@ -1066,44 +1066,44 @@ │ │ │ to dispatch pre-processed events as internal events to the main state │ │ │ machine.

        Using internal events also can make it easier to synchronize the state │ │ │ machines.

        A variant of this is to use a complex state with │ │ │ one state callback, modeling the state │ │ │ with, for example, a tuple {MainFSMState, SubFSMState}.

        To illustrate this we make up an example where the buttons instead generate │ │ │ down and up (press and release) events, and the lock responds │ │ │ to an up event only after the corresponding down event.

        ...
        │ │ │ --export([down/1, up/1]).
        │ │ │ +-export([down/1, up/1]).
        │ │ │  ...
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │  
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ -...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state,maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state,maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │  ...
        │ │ │  
        │ │ │ -open(internal, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(internal, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        If you start this program with code_lock:start([17]) you can unlock with │ │ │ code_lock:down(17), code_lock:up(17).

        │ │ │ │ │ │ │ │ │ │ │ │ Example Revisited │ │ │

        │ │ │ @@ -1131,152 +1131,152 @@ │ │ │ Also, the state diagram does not show that the code_length/0 call │ │ │ must be handled in every state.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: state_functions │ │ │

        │ │ │ -

        Using state functions:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_2).
        │ │ │ +

        Using state functions:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_2).
        │ │ │  
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ --export([down/1,up/1,code_length/0]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([locked/3,open/3]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │ +-export([down/1,up/1,code_length/0]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([locked/3,open/3]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(state_timeout, button, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(state_timeout, button, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        │ │ │ -?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -open(internal, {button,_}, _) ->
        │ │ │ -    {keep_state_and_data, [postpone]};
        │ │ │ +?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +open(internal, {button,_}, _) ->
        │ │ │ +    {keep_state_and_data, [postpone]};
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: handle_event_function │ │ │

        │ │ │

        This section describes what to change in the example to use one │ │ │ handle_event/4 function. The previously used approach to first branch │ │ │ depending on event does not work that well here because of │ │ │ -the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        %%
        │ │ │ +the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        %%
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, locked, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, locked, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  internal, {button,Button}, locked,
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, locked, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, locked, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  internal, {button,Button}, locked,
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, open, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(internal, {button,_}, open, _) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ -handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │ +handle_event(enter, _OldState, open, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(internal, {button,_}, open, _) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ +handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}},
        │ │ │ -              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}},
        │ │ │ +              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event({call,From}, code_length, _State, #{length := Length}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ +handle_event({call,From}, code_length, _State, #{length := Length}) -> │ │ │ + {keep_state_and_data, │ │ │ + [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ seems like a strange thing to do for a code lock, but it at least │ │ │ illustrates event postponing.

        │ │ │ │ │ │ │ │ │ │ │ │ Filter the State │ │ │

        │ │ │ @@ -1286,30 +1286,30 @@ │ │ │ and which digits that remain to unlock.

        This state data can be regarded as sensitive, and maybe not what you want │ │ │ in the error log because of some unpredictable event.

        Another reason to filter the state can be that the state is too large to print, │ │ │ as it fills the error log with uninteresting details.

        To avoid this, you can format the internal state that gets in the error log │ │ │ and gets returned from sys:get_status/1,2 │ │ │ by implementing function │ │ │ Module:format_status/2, │ │ │ for example like this:

        ...
        │ │ │ --export([init/1,terminate/3,format_status/2]).
        │ │ │ +-export([init/1,terminate/3,format_status/2]).
        │ │ │  ...
        │ │ │  
        │ │ │ -format_status(Opt, [_PDict,State,Data]) ->
        │ │ │ +format_status(Opt, [_PDict,State,Data]) ->
        │ │ │      StateData =
        │ │ │ -	{State,
        │ │ │ -	 maps:filter(
        │ │ │ -	   fun (code, _) -> false;
        │ │ │ -	       (_, _) -> true
        │ │ │ +	{State,
        │ │ │ +	 maps:filter(
        │ │ │ +	   fun (code, _) -> false;
        │ │ │ +	       (_, _) -> true
        │ │ │  	   end,
        │ │ │ -	   Data)},
        │ │ │ +	   Data)},
        │ │ │      case Opt of
        │ │ │  	terminate ->
        │ │ │  	    StateData;
        │ │ │  	normal ->
        │ │ │ -	    [{data,[{"State",StateData}]}]
        │ │ │ +	    [{data,[{"State",StateData}]}]
        │ │ │      end.

        It is not mandatory to implement a │ │ │ Module:format_status/2 function. │ │ │ If you do not, a default implementation is used that does the same │ │ │ as this example function without filtering the Data term, that is, │ │ │ StateData = {State, Data}, in this example containing sensitive information.

        │ │ │ │ │ │ │ │ │ @@ -1322,104 +1322,104 @@ │ │ │ like a tuple.

        One reason to use this is when you have a state item that when changed │ │ │ should cancel the state time-out, or one that affects │ │ │ the event handling in combination with postponing events. We will go for │ │ │ the latter and complicate the previous example by introducing │ │ │ a configurable lock button (this is the state item in question), │ │ │ which in the open state immediately locks the door, and an API function │ │ │ set_lock_button/1 to set the lock button.

        Suppose now that we call set_lock_button while the door is open, │ │ │ -and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ -{ok,<0.666.0>}
        │ │ │ -2> code_lock:button(a).
        │ │ │ +and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ +{ok,<0.666.0>}
        │ │ │ +2> code_lock:button(a).
        │ │ │  ok
        │ │ │ -3> code_lock:button(b).
        │ │ │ +3> code_lock:button(b).
        │ │ │  ok
        │ │ │ -4> code_lock:button(c).
        │ │ │ +4> code_lock:button(c).
        │ │ │  ok
        │ │ │  Open
        │ │ │ -5> code_lock:button(y).
        │ │ │ +5> code_lock:button(y).
        │ │ │  ok
        │ │ │ -6> code_lock:set_lock_button(y).
        │ │ │ +6> code_lock:set_lock_button(y).
        │ │ │  x
        │ │ │  % What should happen here?  Immediate lock or nothing?

        We could say that the button was pressed too early so it should not be │ │ │ recognized as the lock button. Or we can make the lock button part of │ │ │ the state so when we then change the lock button in the locked state, │ │ │ the change becomes a state change and all postponed events are retried, │ │ │ therefore the lock is immediately locked!

        We define the state as {StateName, LockButton}, where StateName │ │ │ -is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_3).
        │ │ │ +is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_3).
        │ │ │  
        │ │ │ --export([start_link/2,stop/0]).
        │ │ │ --export([button/1,set_lock_button/1]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([handle_event/4]).
        │ │ │ -
        │ │ │ -start_link(Code, LockButton) ->
        │ │ │ -    gen_statem:start_link(
        │ │ │ -        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ -set_lock_button(LockButton) ->
        │ │ │ -    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, {locked,LockButton}, Data}.
        │ │ │ +-export([start_link/2,stop/0]).
        │ │ │ +-export([button/1,set_lock_button/1]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([handle_event/4]).
        │ │ │ +
        │ │ │ +start_link(Code, LockButton) ->
        │ │ │ +    gen_statem:start_link(
        │ │ │ +        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ +set_lock_button(LockButton) ->
        │ │ │ +    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, {locked,LockButton}, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        │ │ │  
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  cast, {button,Button}, {locked,LockButton},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  cast, {button,Button}, {locked,LockButton},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, {open,LockButton}, Data};
        │ │ │ +            {next_state, {open,LockButton}, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %%
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %%
        │ │ │  %% Common events
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ -  {StateName,OldLockButton}, Data) ->
        │ │ │ -    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ -     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ +  {StateName,OldLockButton}, Data) ->
        │ │ │ +    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ +     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Hibernation │ │ │

        │ │ │

        If you have many servers in one node and they have some state(s) in their │ │ │ @@ -1428,19 +1428,19 @@ │ │ │ footprint of a server can be minimized by hibernating it through │ │ │ proc_lib:hibernate/3.

        Note

        It is rather costly to hibernate a process; see erlang:hibernate/3. It is │ │ │ not something you want to do after every event.

        We can in this example hibernate in the {open, _} state, │ │ │ because what normally occurs in that state is that the state time-out │ │ │ after a while triggers a transition to {locked, _}:

        ...
        │ │ │  %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ -      hibernate]};
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ +      hibernate]};
        │ │ │  ...

        The atom hibernate in the action list on the │ │ │ last line when entering the {open, _} state is the only change. If any event │ │ │ arrives in the {open, _}, state, we do not bother to rehibernate, │ │ │ so the server stays awake after any event.

        To change that we would need to insert action hibernate in more places. │ │ │ For example, the state-independent set_lock_button operation │ │ │ would have to use hibernate but only in the {open, _} state, │ │ │ which would clutter the code.

        Another not uncommon scenario is to use the │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/sup_princ.html │ │ │ @@ -128,48 +128,48 @@ │ │ │ the order specified by this list, and are terminated in the reverse order.

        │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │

        │ │ │

        The callback module for a supervisor starting the server from │ │ │ -gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ --behaviour(supervisor).
        │ │ │ +gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ +-behaviour(supervisor).
        │ │ │  
        │ │ │ --export([start_link/0]).
        │ │ │ --export([init/1]).
        │ │ │ +-export([start_link/0]).
        │ │ │ +-export([init/1]).
        │ │ │  
        │ │ │ -start_link() ->
        │ │ │ -    supervisor:start_link(ch_sup, []).
        │ │ │ +start_link() ->
        │ │ │ +    supervisor:start_link(ch_sup, []).
        │ │ │  
        │ │ │ -init(_Args) ->
        │ │ │ -    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ -    ChildSpecs = [#{id => ch3,
        │ │ │ -                    start => {ch3, start_link, []},
        │ │ │ +init(_Args) ->
        │ │ │ +    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ +    ChildSpecs = [#{id => ch3,
        │ │ │ +                    start => {ch3, start_link, []},
        │ │ │                      restart => permanent,
        │ │ │                      shutdown => brutal_kill,
        │ │ │                      type => worker,
        │ │ │ -                    modules => [ch3]}],
        │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ + modules => [ch3]}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ supervisor flags.

        The ChildSpecs variable in the return value from init/1 is a list of │ │ │ child specifications.

        │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Flags │ │ │

        │ │ │ -

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ -                intensity => non_neg_integer(),   % optional
        │ │ │ -                period => pos_integer(),          % optional
        │ │ │ -                auto_shutdown => auto_shutdown()} % optional
        │ │ │ -    strategy() = one_for_all
        │ │ │ +

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ +                intensity => non_neg_integer(),   % optional
        │ │ │ +                period => pos_integer(),          % optional
        │ │ │ +                auto_shutdown => auto_shutdown()} % optional
        │ │ │ +    strategy() = one_for_all
        │ │ │                 | one_for_one
        │ │ │                 | rest_for_one
        │ │ │                 | simple_one_for_one
        │ │ │ -    auto_shutdown() = never
        │ │ │ +    auto_shutdown() = never
        │ │ │                      | any_significant
        │ │ │                      | all_significant

        │ │ │ │ │ │ │ │ │ │ │ │ @@ -408,28 +408,28 @@ │ │ │ exhaust the Maximum Restart Intensity of the │ │ │ parent supervisor.

        │ │ │ │ │ │ │ │ │ │ │ │ Child Specification │ │ │

        │ │ │ -

        The type definition for a child specification is as follows:

        child_spec() = #{id => child_id(),             % mandatory
        │ │ │ -                 start => mfargs(),            % mandatory
        │ │ │ -                 restart => restart(),         % optional
        │ │ │ -                 significant => significant(), % optional
        │ │ │ -                 shutdown => shutdown(),       % optional
        │ │ │ -                 type => worker(),             % optional
        │ │ │ -                 modules => modules()}         % optional
        │ │ │ -    child_id() = term()
        │ │ │ -    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
        │ │ │ -    modules() = [module()] | dynamic
        │ │ │ -    restart() = permanent | transient | temporary
        │ │ │ -    significant() = boolean()
        │ │ │ -    shutdown() = brutal_kill | timeout()
        │ │ │ -    worker() = worker | supervisor
        • id is used to identify the child specification internally by the supervisor.

          The id key is mandatory.

          Note that this identifier occasionally has been called "name". As far as │ │ │ +

          The type definition for a child specification is as follows:

          child_spec() = #{id => child_id(),             % mandatory
          │ │ │ +                 start => mfargs(),            % mandatory
          │ │ │ +                 restart => restart(),         % optional
          │ │ │ +                 significant => significant(), % optional
          │ │ │ +                 shutdown => shutdown(),       % optional
          │ │ │ +                 type => worker(),             % optional
          │ │ │ +                 modules => modules()}         % optional
          │ │ │ +    child_id() = term()
          │ │ │ +    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
          │ │ │ +    modules() = [module()] | dynamic
          │ │ │ +    restart() = permanent | transient | temporary
          │ │ │ +    significant() = boolean()
          │ │ │ +    shutdown() = brutal_kill | timeout()
          │ │ │ +    worker() = worker | supervisor
          • id is used to identify the child specification internally by the supervisor.

            The id key is mandatory.

            Note that this identifier occasionally has been called "name". As far as │ │ │ possible, the terms "identifier" or "id" are now used but in order to keep │ │ │ backwards compatibility, some occurrences of "name" can still be found, for │ │ │ example in error messages.

          • start defines the function call used to start the child process. It is a │ │ │ module-function-arguments tuple used as apply(M, F, A).

            It is to be (or result in) a call to any of the following:

            The start key is mandatory.

          • restart defines when a terminated child process is to be │ │ │ restarted.

            • A permanent child process is always restarted.
            • A temporary child process is never restarted (not even when the supervisor │ │ │ restart strategy is rest_for_one or one_for_all and a sibling death │ │ │ @@ -457,53 +457,53 @@ │ │ │ supervisor, the default value infinity will be used.

            • type specifies whether the child process is a supervisor or a worker.

              The type key is optional. If it is not given, the default value worker │ │ │ will be used.

            • modules has to be a list consisting of a single element. The value │ │ │ of that element depends on the behaviour of the process:

              • If the child process is a gen_event, the element has to be the atom │ │ │ dynamic.
              • Otherwise, the element should be Module, where Module is the │ │ │ name of the callback module.

              This information is used by the release handler during upgrades and │ │ │ downgrades; see Release Handling.

              The modules key is optional. If it is not given, it defaults to [M], where │ │ │ M comes from the child's start {M,F,A}.

            Example: The child specification to start the server ch3 in the previous │ │ │ -example look as follows:

            #{id => ch3,
            │ │ │ -  start => {ch3, start_link, []},
            │ │ │ +example look as follows:

            #{id => ch3,
            │ │ │ +  start => {ch3, start_link, []},
            │ │ │    restart => permanent,
            │ │ │    shutdown => brutal_kill,
            │ │ │    type => worker,
            │ │ │ -  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │ +  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │    start => {ch3, start_link, []},
            │ │ │    shutdown => brutal_kill}

            Example: A child specification to start the event manager from the chapter about │ │ │ -gen_event:

            #{id => error_man,
            │ │ │ -  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ -  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ +gen_event:

            #{id => error_man,
            │ │ │ +  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ +  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ be always accessible. Thus they are specified to be permanent.

            ch3 does not need to do any cleaning up before termination. Thus, no shutdown │ │ │ time is needed, but brutal_kill is sufficient. error_man can need some time │ │ │ for the event handlers to clean up, thus the shutdown time is set to 5000 ms │ │ │ -(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ -  start => {sup, start_link, []},
            │ │ │ +(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ +  start => {sup, start_link, []},
            │ │ │    restart => transient,
            │ │ │ -  type => supervisor} % will cause default shutdown=>infinity

            │ │ │ + type => supervisor} % will cause default shutdown=>infinity

            │ │ │ │ │ │ │ │ │ │ │ │ Starting a Supervisor │ │ │

            │ │ │

            In the previous example, the supervisor is started by calling │ │ │ -ch_sup:start_link():

            start_link() ->
            │ │ │ -    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ +ch_sup:start_link():

            start_link() ->
            │ │ │ +    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ links to a new process, a supervisor.

            • The first argument, ch_sup, is the name of the callback module, that is, the │ │ │ module where the init callback function is located.
            • The second argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any data and ignores the argument.

            In this case, the supervisor is not registered. Instead its pid must be used. A │ │ │ name can be specified by calling │ │ │ supervisor:start_link({local, Name}, Module, Args) │ │ │ or │ │ │ supervisor:start_link({global, Name}, Module, Args).

            The new supervisor process calls the callback function ch_sup:init([]). init │ │ │ -has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ -    SupFlags = #{},
            │ │ │ -    ChildSpecs = [#{id => ch3,
            │ │ │ -                    start => {ch3, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ +has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ +    SupFlags = #{},
            │ │ │ +    ChildSpecs = [#{id => ch3,
            │ │ │ +                    start => {ch3, start_link, []},
            │ │ │ +                    shutdown => brutal_kill}],
            │ │ │ +    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ specifications in the start specification. In this case there is a single child │ │ │ process, called ch3.

            supervisor:start_link/3 is synchronous. It does not return until all child │ │ │ processes have been started.

            │ │ │ │ │ │ │ │ │ │ │ │ Adding a Child Process │ │ │ @@ -532,31 +532,31 @@ │ │ │ │ │ │ │ │ │ Simplified one_for_one Supervisors │ │ │

            │ │ │

            A supervisor with restart strategy simple_one_for_one is a simplified │ │ │ one_for_one supervisor, where all child processes are dynamically added │ │ │ instances of the same process.

            The following is an example of a callback module for a simple_one_for_one │ │ │ -supervisor:

            -module(simple_sup).
            │ │ │ --behaviour(supervisor).
            │ │ │ +supervisor:

            -module(simple_sup).
            │ │ │ +-behaviour(supervisor).
            │ │ │  
            │ │ │ --export([start_link/0]).
            │ │ │ --export([init/1]).
            │ │ │ +-export([start_link/0]).
            │ │ │ +-export([init/1]).
            │ │ │  
            │ │ │ -start_link() ->
            │ │ │ -    supervisor:start_link(simple_sup, []).
            │ │ │ +start_link() ->
            │ │ │ +    supervisor:start_link(simple_sup, []).
            │ │ │  
            │ │ │ -init(_Args) ->
            │ │ │ -    SupFlags = #{strategy => simple_one_for_one,
            │ │ │ +init(_Args) ->
            │ │ │ +    SupFlags = #{strategy => simple_one_for_one,
            │ │ │                   intensity => 0,
            │ │ │ -                 period => 1},
            │ │ │ -    ChildSpecs = [#{id => call,
            │ │ │ -                    start => {call, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ + period => 1}, │ │ │ + ChildSpecs = [#{id => call, │ │ │ + start => {call, start_link, []}, │ │ │ + shutdown => brutal_kill}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ processes. Instead, all child processes need to be added dynamically by │ │ │ calling supervisor:start_child(Sup, List).

            Sup is the pid, or name, of the supervisor. List is an arbitrary list of │ │ │ terms, which are added to the list of arguments specified in the child │ │ │ specification. If the start function is specified as {M, F, A}, the child │ │ │ process is started by calling apply(M, F, A++List).

            For example, adding a child to simple_sup above:

            supervisor:start_child(Pid, [id1])

            The result is that the child process is started by calling │ │ │ apply(call, start_link, []++[id1]), or actually:

            call:start_link(id1)

            A child under a simple_one_for_one supervisor can be terminated with the │ │ │ following:

            supervisor:terminate_child(Sup, Pid)

            Sup is the pid, or name, of the supervisor and Pid is the pid of the child.

            Because a simple_one_for_one supervisor can have many children, it shuts them │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/tablesdatabases.html │ │ │ @@ -146,73 +146,73 @@ │ │ │ │ │ │ │ │ │ Deleting an Element │ │ │

        │ │ │

        The delete operation is considered successful if the element was not present │ │ │ in the table. Hence all attempts to check that the element is present in the │ │ │ Ets/Mnesia table before deletion are unnecessary. Here follows an example for │ │ │ -Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ -    [] ->
        │ │ │ +Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ +    [] ->
        │ │ │          ok;
        │ │ │ -    [_|_] ->
        │ │ │ -        ets:delete(Tab, Key)
        │ │ │ +    [_|_] ->
        │ │ │ +        ets:delete(Tab, Key)
        │ │ │  end,

        │ │ │ │ │ │ │ │ │ │ │ │ Fetching Data │ │ │

        │ │ │

        Do not fetch data that you already have.

        Consider that you have a module that handles the abstract data type Person. │ │ │ You export the interface function print_person/1, which uses the internal │ │ │ functions print_name/1, print_age/1, and print_occupation/1.

        Note

        If the function print_name/1, and so on, had been interface functions, the │ │ │ situation would have been different, as you do not want the user of the │ │ │ interface to know about the internal data representation.

        DO

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(Person),
        │ │ │ -            print_age(Person),
        │ │ │ -            print_occupation(Person);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(Person),
        │ │ │ +            print_age(Person),
        │ │ │ +            print_occupation(Person);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ +print_name(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.name]).
        │ │ │  
        │ │ │ -print_age(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ +print_age(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.age]).
        │ │ │  
        │ │ │ -print_occupation(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_occupation(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(PersonID),
        │ │ │ -            print_age(PersonID),
        │ │ │ -            print_occupation(PersonID);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(PersonID),
        │ │ │ +            print_age(PersonID),
        │ │ │ +            print_occupation(PersonID);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ -
        │ │ │ -print_age(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ -
        │ │ │ -print_occupation(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ +print_name(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.name]). │ │ │ + │ │ │ +print_age(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.age]). │ │ │ + │ │ │ +print_occupation(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ │ │ │ │ │ │ │ │ │ Non-Persistent Database Storage │ │ │

        │ │ │

        For non-persistent database storage, prefer Ets tables over Mnesia │ │ │ local_content tables. Even the Mnesia dirty_write operations carry a fixed │ │ │ @@ -226,38 +226,38 @@ │ │ │ │ │ │

        Assuming an Ets table that uses idno as key and contains the following:

        [#person{idno = 1, name = "Adam",  age = 31, occupation = "mailman"},
        │ │ │   #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"},
        │ │ │   #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"},
        │ │ │   #person{idno = 4, name = "Carl",  age = 25, occupation = "mailman"}]

        If you must return all data stored in the Ets table, you can use │ │ │ ets:tab2list/1. However, usually you are only interested in a subset of the │ │ │ information in which case ets:tab2list/1 is expensive. If you only want to │ │ │ -extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name='_',
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │                                  "Bryan" ->
        │ │ │ -                                    [X#person.age|Acc];
        │ │ │ +                                    [X#person.age|Acc];
        │ │ │                                   _ ->
        │ │ │                                       Acc
        │ │ │                             end
        │ │ │ -             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ -then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ +then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='_',
        │ │ │ -                          occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ + occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ │ │ │ │ │ │ │ │ │ ordered_set Tables │ │ │

        │ │ │

        If the data in the table is to be accessed so that the order of the keys in the │ │ │ table is significant, the table type ordered_set can be used instead of the │ │ │ @@ -293,20 +293,20 @@ │ │ │ Clearly, the second table would have to be kept consistent with the master │ │ │ table. Mnesia can do this for you, but a home-brew index table can be very │ │ │ efficient compared to the overhead involved in using Mnesia.

        An index table for the table in the previous examples would have to be a bag (as │ │ │ keys would appear more than once) and can have the following contents:

        [#index_entry{name="Adam", idno=1},
        │ │ │   #index_entry{name="Bryan", idno=2},
        │ │ │   #index_entry{name="Bryan", idno=3},
        │ │ │   #index_entry{name="Carl", idno=4}]

        Given this index table, a lookup of the age fields for all persons named │ │ │ -"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ -lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ -                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │ +"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ +lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ +                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │                   Age
        │ │ │            end,
        │ │ │ -          MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ + MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ ets:lookup/2 call. The lists:map/2 call is only used to traverse the idnos │ │ │ matching the name "Bryan" in the table; thus the number of lookups in the master │ │ │ table is minimized.

        Keeping an index table introduces some overhead when inserting records in the │ │ │ table. The number of operations gained from the table must therefore be compared │ │ │ against the number of operations inserting objects in the table. However, notice │ │ │ that the gain is significant when the key can be used to lookup elements.

        │ │ │ │ │ │ @@ -321,51 +321,51 @@ │ │ │ Secondary Index │ │ │

        │ │ │

        If you frequently do lookups on a field that is not the key of the table, you │ │ │ lose performance using mnesia:select() or │ │ │ mnesia:match_object() as these function traverse │ │ │ the whole table. Instead, you can create a secondary index and use │ │ │ mnesia:index_read/3 to get faster access at the expense of using more │ │ │ -memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │ +memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │          ...
        │ │ │ -{atomic, ok} =
        │ │ │ -mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ -                              {attributes,
        │ │ │ -                                    record_info(fields, person)}]),
        │ │ │ -{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │ +{atomic, ok} =
        │ │ │ +mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ +                              {attributes,
        │ │ │ +                                    record_info(fields, person)}]),
        │ │ │ +{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │  ...
        │ │ │  
        │ │ │  PersonsAge42 =
        │ │ │ -     mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ + mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ │ │ │ │ │ │ │ │ │ Transactions │ │ │

        │ │ │

        Using transactions is a way to guarantee that the distributed Mnesia database │ │ │ remains consistent, even when many different processes update it in parallel. │ │ │ However, if you have real-time requirements it is recommended to use dirtry │ │ │ operations instead of transactions. When using dirty operations, you lose the │ │ │ consistency guarantee; this is usually solved by only letting one process update │ │ │ the table. Other processes must send update requests to that process.

        Example:

        ...
        │ │ │  %% Using transaction
        │ │ │  
        │ │ │ -Fun = fun() ->
        │ │ │ -          [mnesia:read({Table, Key}),
        │ │ │ -           mnesia:read({Table2, Key2})]
        │ │ │ +Fun = fun() ->
        │ │ │ +          [mnesia:read({Table, Key}),
        │ │ │ +           mnesia:read({Table2, Key2})]
        │ │ │        end,
        │ │ │  
        │ │ │ -{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │ +{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │  ...
        │ │ │  
        │ │ │  %% Same thing using dirty operations
        │ │ │  ...
        │ │ │  
        │ │ │ -Result1 = mnesia:dirty_read({Table, Key}),
        │ │ │ -Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ +
        Result1 = mnesia:dirty_read({Table, Key}), │ │ │ +Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ │ │ │ │

        map/0 type.

        For convenience, the following types are also built-in. They can be thought as │ │ │ predefined aliases for the type unions also shown in the table.

        Built-in typeDefined as
        term/0any/0
        binary/0<<_:_*8>>
        nonempty_binary/0<<_:8, _:_*8>>
        bitstring/0<<_:_*1>>
        nonempty_bitstring/0<<_:1, _:_*1>>
        boolean/0'false' | 'true'
        byte/00..255
        char/00..16#10ffff
        nil/0[]
        number/0integer/0 | float/0
        list/0[any()]
        maybe_improper_list/0maybe_improper_list(any(), any())
        nonempty_list/0nonempty_list(any())
        string/0[char()]
        nonempty_string/0[char(),...]
        iodata/0iolist() | binary()
        iolist/0maybe_improper_list(byte() | binary() | iolist(), binary() | [])
        map/0#{any() => any()}
        function/0fun()
        module/0atom/0
        mfa/0{module(),atom(),arity()}
        arity/00..255
        identifier/0pid() | port() | reference()
        node/0atom/0
        timeout/0'infinity' | non_neg_integer()
        no_return/0none/0

        Table: Built-in types, predefined aliases

        In addition, the following three built-in types exist and can be thought as │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ according to the type language defined above.

        Built-in typeCan be thought defined by the syntax
        non_neg_integer/00..
        pos_integer/01..
        neg_integer/0..-1

        Table: Additional built-in types

        Note

        The following built-in list types also exist, but they are expected to be │ │ │ -rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ -nonempty_improper_list(Type1, Type2)
        │ │ │ -nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ -shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ -        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ +rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ +nonempty_improper_list(Type1, Type2)
        │ │ │ +nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ +shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ +        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ Type Information in Record Declarations.

        │ │ │ │ │ │ │ │ │ │ │ │ Redefining built-in types │ │ │

        │ │ │

        Change

        Starting from Erlang/OTP 26, it is permitted to define a type having the same │ │ │ name as a built-in type.

        It is recommended to avoid deliberately reusing built-in names because it can be │ │ │ confusing. However, when an Erlang/OTP release introduces a new type, code that │ │ │ happened to define its own type having the same name will continue to work.

        As an example, imagine that the Erlang/OTP 42 release introduces a new type │ │ │ -gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ -for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ +gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ +for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ warning), and Dialyzer will not emit any additional warnings.

        │ │ │ │ │ │ │ │ │ │ │ │ Type Declarations of User-Defined Types │ │ │

        │ │ │

        As seen, the basic syntax of a type is an atom followed by closed parentheses. │ │ │ New types are declared using -type and -opaque attributes as in the │ │ │ -following:

        -type my_struct_type() :: Type.
        │ │ │ --opaque my_opaq_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ +following:

        -type my_struct_type() :: Type.
        │ │ │ +-opaque my_opaq_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ type as defined in the previous section. A current restriction is that Type │ │ │ can contain only predefined types, or user-defined types which are either of the │ │ │ following:

        • Module-local type, that is, with a definition that is present in the code of │ │ │ the module
        • Remote type, that is, type defined in, and exported by, other modules; more │ │ │ about this soon.

        For module-local types, the restriction that their definition exists in the │ │ │ module is enforced by the compiler and results in a compilation error. (A │ │ │ similar restriction currently exists for records.)

        Type declarations can also be parameterized by including type variables between │ │ │ the parentheses. The syntax of type variables is the same as Erlang variables, │ │ │ that is, starts with an upper-case letter. These variables is to │ │ │ -appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ -refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ -them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ -mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ +appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ +refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ +them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ +mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ supposed to be visible from outside of their defining module. That is, only the │ │ │ module defining them is allowed to depend on their term structure. Consequently, │ │ │ such types do not make much sense as module local - module local types are not │ │ │ accessible by other modules anyway - and is always to be exported.

        Read more on Opaques

        │ │ │ │ │ │ │ │ │ │ │ │ Type Information in Record Declarations │ │ │

        │ │ │

        The types of record fields can be specified in the declaration of the record. │ │ │ -The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ -previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ -the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ +The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ +previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ +the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ the corresponding types. This is checked by the compiler and results in a │ │ │ compilation error if a violation is detected.

        Change

        Before Erlang/OTP 19, for fields without initial values, the singleton type │ │ │ 'undefined' was added to all declared types. In other words, the following │ │ │ -two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ -             f2      :: float(),
        │ │ │ -             f3      :: 'a' | 'b'}).
        │ │ │ +two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ +             f2      :: float(),
        │ │ │ +             f3      :: 'a' | 'b'}).
        │ │ │  
        │ │ │ --record(rec, {f1 = 42 :: integer(),
        │ │ │ -              f2      :: 'undefined' | float(),
        │ │ │ -              f3      :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ +-record(rec, {f1 = 42 :: integer(), │ │ │ + f2 :: 'undefined' | float(), │ │ │ + f3 :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ type, you must explicitly add it to the typespec, as in the 2nd example.

        Any record, containing type information or not, once defined, can be used as a │ │ │ type using the following syntax:

        #rec{}

        In addition, the record fields can be further specified when using a record type │ │ │ by adding type information about the field as follows:

        #rec{some_field :: Type}

        Any unspecified fields are assumed to have the type in the original record │ │ │ declaration.

        Note

        When records are used to create patterns for ETS and Mnesia match functions, │ │ │ -Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ --record(person, {name :: string(), height :: height()}).
        │ │ │ +Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ +-record(person, {name :: string(), height :: height()}).
        │ │ │  
        │ │ │ -lookup(Name, Tab) ->
        │ │ │ -    ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ +lookup(Name, Tab) -> │ │ │ + ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ height.

        The recommended way of dealing with this is to declare the smallest record │ │ │ field types to accommodate all your needs, and then create refinements as │ │ │ -needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │ +needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │  
        │ │ │ --type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ +-type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ before #person{}.

        │ │ │ │ │ │ │ │ │ │ │ │ Specifications for Functions │ │ │

        │ │ │

        A specification (or contract) for a function is given using the -spec │ │ │ attribute. The general format is as follows:

        -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        An implementation of the function with the same name Function must exist in │ │ │ the current module, and the arity of the function must match the number of │ │ │ arguments, otherwise the compilation fails.

        The following longer format with module name is also valid as long as Module │ │ │ is the name of the current module. This can be useful for documentation │ │ │ purposes.

        -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        Also, for documentation purposes, argument names can be given:

        -spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

        A function specification can be overloaded. That is, it can have several types, │ │ │ -separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ -         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ +separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ +         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ the domains of the argument types cannot overlap. For example, the following │ │ │ -specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ -         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ +specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ +         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ and output arguments of a function. For example, the following specification │ │ │ defines the type of a polymorphic identity function:

        -spec id(X) -> X.

        Notice that the above specification does not restrict the input and output type │ │ │ in any way. These types can be constrained by guard-like subtype constraints and │ │ │ -provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ +provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ constraint that can be used in the when part of a -spec attribute.

        Note

        The above function specification uses multiple occurrences of the same type │ │ │ variable. That provides more type information than the following function │ │ │ -specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ +specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ some tuple. The specification with the X type variable specifies that the │ │ │ function takes a tuple and returns the same tuple.

        However, it is up to the tools that process the specifications to choose │ │ │ whether to take this extra information into account or not.

        The scope of a :: constraint is the (...) -> RetType specification after │ │ │ which it appears. To avoid confusion, it is suggested that different variables │ │ │ are used in different constituents of an overloaded contract, as shown in the │ │ │ -following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ -         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ +following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ +         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ servers or because they are used to throw exceptions, as in the following │ │ │ -function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ +function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ for their "return", through a contract of the following form:

        -spec my_error(term()) -> no_return().

        Note

        Erlang uses the shorthand version _ as an anonymous type variable equivalent │ │ │ to term/0 or any/0. For example, the following function

        -spec Function(string(), _) -> string().

        is equivalent to:

        -spec Function(string(), any()) -> string().
        │ │ │
        │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/upcoming_incompatibilities.html │ │ │ @@ -149,45 +149,45 @@ │ │ │ occurrences of maybe without quotes.

        │ │ │ │ │ │ │ │ │ │ │ │ 0.0 and -0.0 will no longer be exactly equal │ │ │

        │ │ │

        Currently, the floating point numbers 0.0 and -0.0 have distinct internal │ │ │ -representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ -<<0,0,0,0,0,0,0,0>>
        │ │ │ -2> <<-0.0/float>>.
        │ │ │ -<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ +representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ +<<0,0,0,0,0,0,0,0>>
        │ │ │ +2> <<-0.0/float>>.
        │ │ │ +<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ operator, they are considered to be equal. Thus, 0.0 =:= -0.0 currently │ │ │ returns true.

        In Erlang/OTP 27, 0.0 =:= -0.0 will return false, and matching 0.0 against │ │ │ -0.0 will fail. When used as map keys, 0.0 and -0.0 will be considered to │ │ │ be distinct.

        The == operator will continue to return true for 0.0 == -0.0.

        To help to find code that might need to be revised, in OTP 27 there will be a │ │ │ new compiler warning when matching against 0.0 or comparing to that value │ │ │ using the =:= operator. The warning can be suppressed by matching against │ │ │ +0.0 instead of 0.0.

        We plan to introduce the same warning in OTP 26.1, but by default it will be │ │ │ disabled.

        │ │ │ │ │ │ │ │ │ │ │ │ Singleton type variables will become a compile-time error │ │ │

        │ │ │ -

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ -    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ -f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ -Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │ +

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ +    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ +f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ +Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │  %    6|     Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │  %     |                  ^

        In OTP 27, that warning will become an error.

        │ │ │ │ │ │ │ │ │ │ │ │ Escripts will be compiled by default │ │ │

        │ │ │

        Escripts will be compiled by default instead of interpreted. That means that the │ │ │ compiler application must be available.

        The old behavior of interpreting escripts can be restored by adding the │ │ │ -following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ +following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ │ │ │ │ │ │ │ │ │ -code_path_choice will default to strict │ │ │

        │ │ │

        This command line option controls if paths given in the command line, boot │ │ │ scripts, and the code server should be interpreted as is strict or relaxed.

        OTP 26 and earlier defaults to relaxed, which means -pa myapp/ebin would │ │ │ @@ -231,18 +231,18 @@ │ │ │ " │ │ │ String Content │ │ │ " │ │ │ %% │ │ │ %% In OTP 27 it is instead interpreted as a │ │ │ %% Triple-Quoted String equivalent to │ │ │ "String Content"

        """"
        │ │ │ -++ foo() ++
        │ │ │ +++ foo() ++
        │ │ │  """"
        │ │ │  %% Became
        │ │ │ -"" ++ foo() ++ ""
        │ │ │ +"" ++ foo() ++ ""
        │ │ │  %%
        │ │ │  %% In OTP 27 it is instead interpreted as a
        │ │ │  %% Triple-Quoted String (triple-or-more) equivalent to
        │ │ │  "++ foo() ++"

        From Erlang/OTP 26.1 up to 27.0 the compiler issues a warning for a sequence of │ │ │ 3 or more double-quote characters since that is almost certainly a mistake or │ │ │ something like a result of bad automatic code generation. If a users gets that │ │ │ warning, the code should be corrected for example by inserting appropriate │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/alt_dist.html │ │ │ @@ -237,50 +237,50 @@ │ │ │ uds_dist example using a port driver written in C, erl_uds_dist is written │ │ │ entirely in Erlang.

        │ │ │ │ │ │ │ │ │ │ │ │ Exported Callback Functions │ │ │

        │ │ │ -

        The following functions are mandatory:

        • listen(Name) ->
          │ │ │ -  {ok, {Listen, Address, Creation}} | {error, Error}
          │ │ │ -listen(Name,Host) ->
          │ │ │ -  {ok, {Listen, Address, Creation}} | {error, Error}

          listen/2 is called once in order to listen for incoming connection requests. │ │ │ +

          The following functions are mandatory:

          • listen(Name) ->
            │ │ │ +  {ok, {Listen, Address, Creation}} | {error, Error}
            │ │ │ +listen(Name,Host) ->
            │ │ │ +  {ok, {Listen, Address, Creation}} | {error, Error}

            listen/2 is called once in order to listen for incoming connection requests. │ │ │ The call is made when the distribution is brought up. The argument Name is │ │ │ the part of the node name before the @ sign in the full node name. It can be │ │ │ either an atom or a string. The argument Host is the part of the node name │ │ │ after the @ sign in the full node name. It is always a string.

            The return value consists of a Listen handle (which is later passed to the │ │ │ accept/1 callback), Address which is a │ │ │ #net_address{} record with information about the address for the node (the │ │ │ #net_address{} record is defined in kernel/include/net_address.hrl), and │ │ │ Creation which (currently) is an integer 1, 2, or 3.

            If epmd is to be used for node discovery, you typically want │ │ │ to use the erl_epmd module (part of the kernel application) in order to │ │ │ -register the listen port with epmd and retrieve Creation to use.

          • address() ->
            │ │ │ +register the listen port with epmd and retrieve Creation to use.

          • address() ->
            │ │ │    Address

            address/0 is called in order to get the Address part of the │ │ │ listen/2 function without creating a listen socket. │ │ │ -All fields except address have to be set in the returned record

            Example:

            address() ->
            │ │ │ -    {ok, Host} = inet:gethostname(),
            │ │ │ -    #net_address{ host = Host, protocol = tcp, family = inet6 }.
          • accept(Listen) ->
            │ │ │ +All fields except address have to be set in the returned record

            Example:

            address() ->
            │ │ │ +    {ok, Host} = inet:gethostname(),
            │ │ │ +    #net_address{ host = Host, protocol = tcp, family = inet6 }.
          • accept(Listen) ->
            │ │ │    AcceptorPid

            accept/1 should spawn a process that accepts connections. This process │ │ │ should preferably execute on max priority. The process identifier of this │ │ │ process should be returned.

            The Listen argument will be the same as the Listen handle part of the │ │ │ return value of the listen/1 callback above. │ │ │ accept/1 is called only once when the distribution protocol is started.

            The caller of this function is a representative for net_kernel (this may or │ │ │ may not be the process registered as net_kernel) and is in this document │ │ │ identified as Kernel. When a connection has been accepted by the acceptor │ │ │ process, it needs to inform Kernel about the accepted connection. This is │ │ │ -done by passing a message on the form:

            Kernel ! {accept, AcceptorPid, DistController, Family, Proto}

            DistController is either the process or port identifier of the distribution │ │ │ +done by passing a message on the form:

            Kernel ! {accept, AcceptorPid, DistController, Family, Proto}

            DistController is either the process or port identifier of the distribution │ │ │ controller for the connection. The distribution controller should be created │ │ │ by the acceptor processes when a new connection is accepted. Its job is to │ │ │ dispatch traffic on the connection.

            Kernel responds with one of the following messages:

            • {Kernel, controller, SupervisorPid} - The request was accepted and │ │ │ SupervisorPid is the process identifier of the connection supervisor │ │ │ process (which is created in the │ │ │ accept_connection/5 callback).

            • {Kernel, unsupported_protocol} - The request was rejected. This is a │ │ │ fatal error. The acceptor process should terminate.

            When an accept sequence has been completed the acceptor process is expected to │ │ │ -continue accepting further requests.

          • accept_connection(AcceptorPid, DistCtrl, MyNode, Allowed, SetupTime) ->
            │ │ │ +continue accepting further requests.

          • accept_connection(AcceptorPid, DistCtrl, MyNode, Allowed, SetupTime) ->
            │ │ │    ConnectionSupervisorPid

            accept_connection/5 should spawn a process that will perform the Erlang │ │ │ distribution handshake for the connection. If the handshake successfully │ │ │ completes it should continue to function as a connection supervisor. This │ │ │ process should preferably execute on max priority and should be linked to │ │ │ the caller. The dist_util:net_ticker_spawn_options() function can be called │ │ │ to get spawn options suitable for this process which can be passed directly to │ │ │ erlang:spawn_opt/4. dist_util:net_ticker_spawn_options() will by default │ │ │ @@ -294,15 +294,15 @@ │ │ │ dist_util:handshake_other_started(HsData).

          • Allowed - To be passed along to │ │ │ dist_util:handshake_other_started(HsData).

          • SetupTime - Time used for creating a setup timer by a call to │ │ │ dist_util:start_timer(SetupTime). The timer should be passed along to │ │ │ dist_util:handshake_other_started(HsData).

          The created process should provide callbacks and other information needed for │ │ │ the handshake in a #hs_data{} record and call │ │ │ dist_util:handshake_other_started(HsData) with this record.

          dist_util:handshake_other_started(HsData) will perform the handshake and if │ │ │ the handshake successfully completes this process will then continue in a │ │ │ -connection supervisor loop as long as the connection is up.

        • setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
          │ │ │ +connection supervisor loop as long as the connection is up.

        • setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
          │ │ │    ConnectionSupervisorPid

          setup/5 should spawn a process that connects to Node. When connection has │ │ │ been established it should perform the Erlang distribution handshake for the │ │ │ connection. If the handshake successfully completes it should continue to │ │ │ function as a connection supervisor. This process should preferably execute on │ │ │ max priority and should be linked to the caller. The │ │ │ dist_util:net_ticker_spawn_options() function can be called to get spawn │ │ │ options suitable for this process which can be passed directly to │ │ │ @@ -320,23 +320,23 @@ │ │ │ may not be the process registered as net_kernel) and is in this document │ │ │ identified as Kernel.

          This function should, besides spawning the connection supervisor, also create │ │ │ a distribution controller. The distribution controller is either a process or │ │ │ a port which is responsible for dispatching traffic.

          The created process should provide callbacks and other information needed for │ │ │ the handshake in a #hs_data{} record and call │ │ │ dist_util:handshake_we_started(HsData) with this record.

          dist_util:handshake_we_started(HsData) will perform the handshake and the │ │ │ handshake successfully completes this process will then continue in a │ │ │ -connection supervisor loop as long as the connection is up.

        • close(Listen) ->
          │ │ │ -  void()

          Called in order to close the Listen handle that originally was passed from │ │ │ -the listen/1 callback.

        • select(NodeName) ->
          │ │ │ -  boolean()

          Return true if the host name part of the NodeName is valid for use with │ │ │ -this protocol; otherwise, false.

        There are also two optional functions that may be exported:

        • setopts(Listen, Opts) ->
          │ │ │ -  ok | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ +connection supervisor loop as long as the connection is up.

        • close(Listen) ->
          │ │ │ +  void()

          Called in order to close the Listen handle that originally was passed from │ │ │ +the listen/1 callback.

        • select(NodeName) ->
          │ │ │ +  boolean()

          Return true if the host name part of the NodeName is valid for use with │ │ │ +this protocol; otherwise, false.

        There are also two optional functions that may be exported:

        • setopts(Listen, Opts) ->
          │ │ │ +  ok | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ listen/1 callback. The argument Opts is a list of │ │ │ -options to set on future connections.

        • getopts(Listen, Opts) ->
          │ │ │ -  {ok, OptionValues} | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ +options to set on future connections.

        • getopts(Listen, Opts) ->
          │ │ │ +  {ok, OptionValues} | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ listen/1 callback. The argument Opts is a list of │ │ │ options to read for future connections.

        │ │ │ │ │ │ │ │ │ │ │ │ The #hs_data{} Record │ │ │

        │ │ │ @@ -350,44 +350,44 @@ │ │ │ accept_connection/5.

      • other_node - Name of the other node. This field │ │ │ is only mandatory when this node initiates the connection. That is, when │ │ │ connection is set up via setup/5.

      • this_node - The node name of this node.

      • socket - The identifier of the distribution │ │ │ controller.

      • timer - The timer created using │ │ │ dist_util:start_timer/1.

      • allowed - Information passed as Allowed to │ │ │ accept_connection/5. This field is only mandatory when the remote node │ │ │ initiated the connection. That is, when the connection is set up via │ │ │ -accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ -is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ +accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ +is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ Length is 0, all available bytes should be returned. If Length > 0, │ │ │ exactly Length bytes should be returned, or an error; possibly discarding │ │ │ less than Length bytes of data when the connection is closed from the other │ │ │ side. It is used for passive receive of data from the other end.

        Only used during handshake phase.

      • f_setopts_pre_nodeup - A fun with the │ │ │ -following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ just before the distribution channel is taken up for normal traffic.

        Only used during handshake phase.

      • f_setopts_post_nodeup - A fun with │ │ │ -the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ -just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ +the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ the identifier of the low level entity that handles the connection (often │ │ │ -DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node on the other end, and NetAddress is a │ │ │ #net_address{} record with information about the address for the Node on │ │ │ the other end of the connection. The #net_address{} record is defined in │ │ │ -kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ +kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ function should send information over the connection that is not interpreted │ │ │ by the other end while increasing the statistics of received packets on the │ │ │ other end. This is usually implemented by sending an empty packet.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ -a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ +a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ is received packets, Sent is sent packets, and PendSend is amount of data │ │ │ in queue to be sent (typically in bytes, but dist_util only checks whether │ │ │ the value is non-zero to know there is data in queue) or a boolean/0 │ │ │ indicating whether there are packets in queue to be sent.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • request_type - The request Type as passed to │ │ │ setup/5. This is only mandatory when the connection has │ │ │ -been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ -is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ is a list of options to read for the connection.

        This function is optional. Used when connection is up.

      • f_handshake_complete - A fun with the │ │ │ -following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node connected at the other end, and DHandle is a │ │ │ distribution handle needed by a distribution controller process when calling │ │ │ the following BIFs:

        This function is called when the handshake has completed and the distribution │ │ │ channel is up. The distribution controller can begin dispatching traffic over │ │ │ the channel. This function is optional.

        Only used during handshake phase.

      • add_flags - │ │ │ Distribution flags to add to the connection. │ │ │ Currently all (non obsolete) flags will automatically be enabled.

        This flag field is optional.

      • reject_flags - │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/automaticyieldingofccode.html │ │ │ @@ -209,17 +209,17 @@ │ │ │ they have to follow certain restrictions. The convention for making │ │ │ this clear is to have a comment above the function that explains that │ │ │ the function is transformed by YCF (see maps_values_1_helper in │ │ │ erl_map.c for an example). If only the transformed version of the │ │ │ function is used, the convention is to "comment out" the source for the │ │ │ function by surrounding it with the following #ifdef (this way, one │ │ │ will not get warnings about unused functions):

        #ifdef INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS
        │ │ │ -void my_fun() {
        │ │ │ +void my_fun() {
        │ │ │      ...
        │ │ │ -}
        │ │ │ +}
        │ │ │  #endif /* INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS */

        While editing the function one can define │ │ │ INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS so that one can see errors │ │ │ and warnings in the non-transformed source.

        │ │ │ │ │ │ │ │ │ │ │ │ Where to Place YCF Transformed Functions │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/beam_makeops.html │ │ │ @@ -151,17 +151,17 @@ │ │ │ The loader translates generic instructions to specific instructions. │ │ │ In general, for each generic instruction, there exists a family of │ │ │ specific instructions. The OTP 20 release has 389 specific │ │ │ instructions.

      • The implementation of specific instructions for the traditional │ │ │ BEAM interpreter. For the BeamAsm JIT introduced │ │ │ in OTP 24, the implementation of instructions are defined in emitter │ │ │ functions written in C++.

      Generic instructions have typed operands. Here are a few examples of │ │ │ -operands for move/2:

      {move,{atom,id},{x,5}}.
      │ │ │ -{move,{x,3},{x,0}}.
      │ │ │ -{move,{x,2},{y,1}}.

      When those instructions are loaded, the loader rewrites them │ │ │ +operands for move/2:

      {move,{atom,id},{x,5}}.
      │ │ │ +{move,{x,3},{x,0}}.
      │ │ │ +{move,{x,2},{y,1}}.

      When those instructions are loaded, the loader rewrites them │ │ │ to specific instructions:

      move_cx id 5
      │ │ │  move_xx 3 0
      │ │ │  move_xy 2 1

      Corresponding to each generic instruction, there is a family of │ │ │ specific instructions. The types that an instance of a specific │ │ │ instruction can handle are encoded in the instruction names. For │ │ │ example, move_xy takes an X register number as the first operand and │ │ │ a Y register number as the second operand. move_cx takes a tagged │ │ │ @@ -185,17 +185,17 @@ │ │ │ move c x

    Each specific instructions is defined by following the name of the │ │ │ instruction with the types for each operand. An operand type is a │ │ │ single letter. For example, x means an X register, y │ │ │ means a Y register, and c is a "constant" (a tagged term such as │ │ │ an integer, an atom, or a literal).

    Now let's look at the implementation of the move instruction. There │ │ │ are multiple files containing implementations of instructions in the │ │ │ erts/emulator/beam/emu directory. The move instruction is defined │ │ │ -in instrs.tab. It looks like this:

    move(Src, Dst) {
    │ │ │ +in instrs.tab.  It looks like this:

    move(Src, Dst) {
    │ │ │      $Dst = $Src;
    │ │ │ -}

    The implementation for an instruction largely follows the C syntax, │ │ │ +}

    The implementation for an instruction largely follows the C syntax, │ │ │ except that the variables in the function head don't have any types. │ │ │ The $ before an identifier denotes a macro expansion. Thus, │ │ │ $Src will expand to the code to pick up the source operand for │ │ │ the instruction and $Dst to the code for the destination register.

    We will look at the code for each specific instruction in turn. To │ │ │ make the code easier to understand, let's first look at the memory │ │ │ layout for the instruction {move,{atom,id},{x,5}}:

         +--------------------+--------------------+
    │ │ │  I -> |                 40 |       &&lb_move_cx |
    │ │ │ @@ -204,61 +204,61 @@
    │ │ │       +--------------------+--------------------+

    This example and all other examples in the document assumes a 64-bit │ │ │ architecture, and furthermore that pointers to C code fit in 32 bits.

    I in the BEAM virtual machine is the instruction pointer. When BEAM │ │ │ executes an instruction, I points to the first word of the │ │ │ instruction.

    &&lb_move_cx is the address to C code that implements move_cx. It │ │ │ is stored in the lower 32 bits of the word. In the upper 32 bits is │ │ │ the byte offset to the X register; the register number 5 has been │ │ │ multiplied by the word size size 8.

    In the next word the tagged atom id is stored.

    With that background, we can look at the generated code for move_cx │ │ │ -in beam_hot.h:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +in beam_hot.h:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    We will go through each line in turn.

    • OpCase(move_cx): defines a label for the instruction. The │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    We will go through each line in turn.

    • OpCase(move_cx): defines a label for the instruction. The │ │ │ OpCase() macro is defined in beam_emu.c. It will expand this line │ │ │ to lb_move_cx:.

    • BeamInstr next_pf = BeamCodeAddr(I[2]); fetches the pointer to │ │ │ code for the next instruction to be executed. The BeamCodeAddr() │ │ │ macro extracts the pointer from the lower 32 bits of the instruction │ │ │ word.

    • xb(BeamExtraData(I[0])) = I[1]; is the expansion of $Dst = $Src. │ │ │ BeamExtraData() is a macro that will extract the upper 32 bits from │ │ │ the instruction word. In this example, it will return 40 which is the │ │ │ byte offset for X register 5. The xb() macro will cast a byte │ │ │ pointer to an Eterm pointer and dereference it. The I[1] on │ │ │ the right-hand side of the = fetches an Erlang term (the atom id in │ │ │ this case).

    • I += 2 advances the instruction pointer to the next │ │ │ instruction.

    • In a debug-compiled emulator, ASSERT(VALID_INSTR(next_pf)); makes │ │ │ sure that next_pf is a valid instruction (that is, that it points │ │ │ -within the process_main() function in beam_emu.c).

    • GotoPF(next_pf); transfers control to the next instruction.

    Now let's look at the implementation of move_xx:

    OpCase(move_xx):
    │ │ │ -{
    │ │ │ -  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │ +within the process_main() function in beam_emu.c).

  • GotoPF(next_pf); transfers control to the next instruction.

  • Now let's look at the implementation of move_xx:

    OpCase(move_xx):
    │ │ │ +{
    │ │ │ +  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    We will go through the lines that are new or have changed compared to │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    We will go through the lines that are new or have changed compared to │ │ │ move_cx.

    • Eterm tmp_packed1 = BeamExtraData(I[0]); picks up both X register │ │ │ numbers packed into the upper 32 bits of the instruction word.

    • BeamInstr next_pf = BeamCodeAddr(I[1]); pre-fetches the address of │ │ │ the next instruction. Note that because both X registers operands fits │ │ │ into the instruction word, the next instruction is in the very next │ │ │ word.

    • xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK); │ │ │ copies the source to the destination. (For a 64-bit architecture, │ │ │ BEAM_TIGHT_SHIFT is 16 and BEAM_TIGHT_MASK is 0xFFFF.)

    • I += 1; advances the instruction pointer to the next instruction.

    move_xy is almost identical to move_xx. The only difference is │ │ │ the use of the yb() macro instead of xb() to reference the │ │ │ -destination register:

    OpCase(move_xy):
    │ │ │ -{
    │ │ │ -  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  yb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │ +destination register:

    OpCase(move_xy):
    │ │ │ +{
    │ │ │ +  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  yb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    │ │ │ │ │ │ │ │ │ │ │ │ Transformation rules │ │ │

    │ │ │

    Next let's look at how we can do some optimizations using transformation │ │ │ rules. For simple instructions such as move/2, the instruction dispatch │ │ │ @@ -271,21 +271,21 @@ │ │ │ with an uppercase letter just as in Erlang. A pattern variable may be │ │ │ followed = and one or more type letters to constrain the match to │ │ │ one of those types. The variables that are bound on the left-hand side can │ │ │ be used on the right-hand side.

    We will also need to define a specific instruction and an implementation:

    # In ops.tab
    │ │ │  move2 x y x y
    │ │ │  
    │ │ │  // In instrs.tab
    │ │ │ -move2(S1, D1, S2, D2) {
    │ │ │ +move2(S1, D1, S2, D2) {
    │ │ │      Eterm V1, V2;
    │ │ │      V1 = $S1;
    │ │ │      V2 = $S2;
    │ │ │      $D1 = V1;
    │ │ │      $D2 = V2;
    │ │ │ -}

    When the loader has found a match and replaced the matched instructions, │ │ │ +}

    When the loader has found a match and replaced the matched instructions, │ │ │ it will match the new instructions against the transformation rules. │ │ │ Because of that, we can define the rule for a move3/6 instruction │ │ │ as follows:

    move2 X1=x Y1=y X2=x Y2=y | move X3=x Y3=y =>
    │ │ │        move3 X1 Y1 X2 Y2 X3 Y3

    (For readability, a long transformation line can be broken after | │ │ │ and => operators.)

    It would also be possible to define it like this:

    move X1=x Y1=y | move X2=x Y2=y | move X3=x Y3=y =>
    │ │ │       move3 X1 Y1 X2 Y2 X3 Y3

    but in that case it must be defined before the rule for move2/4 │ │ │ because the first matching rule will be applied.

    One must be careful not to create infinite loops. For example, if we │ │ │ @@ -433,29 +433,29 @@ │ │ │ i_bs_get_integer_32 x f? x │ │ │ %endif

    The specific instruction i_bs_get_integer_32 will only be defined │ │ │ on a 64-bit machine.

    The condition can be inverted by using %unless instead of %if:

    %unless NO_FPE_SIGNALS
    │ │ │  fcheckerror p => i_fcheckerror
    │ │ │  i_fcheckerror
    │ │ │  fclearerror
    │ │ │  %endif

    It is also possible to add an %else clause:

    %if ARCH_64
    │ │ │ -BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ -    Uint64 res = ($A) * ($B);
    │ │ │ -    if (res / $B != $A) {
    │ │ │ +BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ +    Uint64 res = ($A) * ($B);
    │ │ │ +    if (res / $B != $A) {
    │ │ │          $Fail;
    │ │ │ -    }
    │ │ │ +    }
    │ │ │      $Dst = res;
    │ │ │ -}
    │ │ │ +}
    │ │ │  %else
    │ │ │ -BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ -    Uint64 res = (Uint64)($A) * (Uint64)($B);
    │ │ │ -    if ((res >> (8*sizeof(Uint))) != 0) {
    │ │ │ +BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ +    Uint64 res = (Uint64)($A) * (Uint64)($B);
    │ │ │ +    if ((res >> (8*sizeof(Uint))) != 0) {
    │ │ │          $Fail;
    │ │ │ -    }
    │ │ │ +    }
    │ │ │      $Dst = res;
    │ │ │ -}
    │ │ │ +}
    │ │ │  %endif

    Symbols that are defined in directives

    The following symbols are always defined.

    • ARCH_64 - is 1 for a 64-bit machine, and 0 otherwise.
    • ARCH_32 - is 1 for 32-bit machine, and 0 otherwise.

    The Makefile for building the emulator currently defines the │ │ │ following symbols by using the -D option on the command line for │ │ │ beam_makeops.

    • USE_VM_PROBES - 1 if the runtime system is compiled to use VM │ │ │ probes (support for dtrace or systemtap), 0 otherwise.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -676,15 +676,15 @@ │ │ │ match both source and destination registers. As an operand in a specific │ │ │ instruction, it must only be used for a destination register.)

  • o - Overflow. An untagged integer that does not fit in a machine word.

  • Predicates

    If the constraints described so far is not enough, additional │ │ │ constraints can be implemented in C and be called as a guard function │ │ │ on the left-hand side of the transformation. If the guard function returns │ │ │ a non-zero value, the matching of the rule will continue, otherwise │ │ │ the match will fail. Such guard functions are hereafter called │ │ │ predicates.

    The most commonly used guard constraints is equal(). It can be used │ │ │ -to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ +to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ predicates.tab in several directories. In predicates.tab directly │ │ │ in $ERL_TOP/erts/emulator/beam, predicates that are used by both the │ │ │ traditinal emulator and the JIT implementations are contained. │ │ │ Predicates only used by the emulator can be found in │ │ │ emu/predicates.tab.

    │ │ │ │ │ │ │ │ │ @@ -692,41 +692,41 @@ │ │ │ A very brief note on implementation of predicates │ │ │

    │ │ │

    It is outside the scope for this document to describe in detail how │ │ │ predicates are implemented because it requires knowledge of the │ │ │ internal loader data structures, but here is quick look at the │ │ │ implementation of a simple predicate called literal_is_map().

    Here is first an example how it is used:

    ismap Fail Lit=q | literal_is_map(Lit) =>

    If the Lit operand is a literal, then the literal_is_map() │ │ │ predicate is called to determine whether it is a map literal. │ │ │ -If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │ +If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │      Eterm term;
    │ │ │  
    │ │ │ -    ASSERT(Lit.type == TAG_q);
    │ │ │ -    term = beamfile_get_literal(&S->beam, Lit.val);
    │ │ │ -    return is_map(term);
    │ │ │ -}

    The pred. prefix tells beam_makeops that this function is a │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = beamfile_get_literal(&S->beam, Lit.val); │ │ │ + return is_map(term); │ │ │ +}

    The pred. prefix tells beam_makeops that this function is a │ │ │ predicate. Without the prefix, it would have been interpreted as the │ │ │ implementation of an instruction (described in Defining the │ │ │ implementation).

    Predicate functions have a magic variabled called S, which is a │ │ │ pointer to a state struct. In the example, │ │ │ beamfile_get_literal(&S->beam, Lit.val); is used to retrieve the actual term │ │ │ for the literal.

    At the time of writing, the expanded C code generated by │ │ │ -beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │ +beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │    Eterm term;
    │ │ │  
    │ │ │ -  ASSERT(Lit.type == TAG_q);
    │ │ │ -  term = S->literals[Lit.val].term;
    │ │ │ -  return is_map(term);;
    │ │ │ -}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = S->literals[Lit.val].term; │ │ │ + return is_map(term);; │ │ │ +}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ number of operands. Such instructions have a {list,[...]} operand │ │ │ -as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ +generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ operand type * like this:

    select_val Src=aiq Fail=f Size=u List=* =>
    │ │ │      i_const_select_val Src Fail Size List

    This transformation renames a select_val/3 instruction │ │ │ with a constant source operand to i_const_select_val/3.

    Constructing new instructions on the right-hand side

    The most common operand on the right-hand side is a variable that was │ │ │ bound while matching the pattern on the left-hand side. For example:

    trim N Remaining => i_trim N

    An operand can also be a type letter to construct an operand of that │ │ │ type. Each type has a default value. For example, the type x has │ │ │ the default value 1023, which is the highest X register. That makes │ │ │ x on the right-hand side a convenient shortcut for a temporary X │ │ │ @@ -746,53 +746,53 @@ │ │ │ transformation rule.

    • u - Construct an untagged integer. The default value is 0.

    • x - X register. The default value is 1023. That makes x convenient to │ │ │ use as a temporary X register.

    • y - Y register. The default value is 0.

    • l - Floating point register number. The default value is 0.

    • i - Tagged literal integer. The default value is 0.

    • a - Tagged atom. The default value is the empty atom (am_Empty).

    • p - Zero failure label.

    • n - NIL ([], the empty list).

    Function call on the right-hand side

    Transformations that are not possible to describe with the rule │ │ │ language as described here can be implemented as a generator function │ │ │ in C and called from the right-hand side of a transformation. The left-hand │ │ │ side of the transformation will perform the match and bind operands to │ │ │ variables. The variables can then be passed to a generator function │ │ │ on the right-hand side. For example:

    bif2 Fail=j u$bif:erlang:element/2 Index=s Tuple=xy Dst=d =>
    │ │ │ -    element(Jump, Index, Tuple, Dst)

    This transformation rule matches a call to the BIF element/2. │ │ │ + element(Jump, Index, Tuple, Dst)

    This transformation rule matches a call to the BIF element/2. │ │ │ The operands will be captured and the generator function element() will │ │ │ be called.

    The element() generator will produce one of two instructions │ │ │ depending on Index. If Index is an integer in the range from 1 up │ │ │ to the maximum tuple size, the instruction i_fast_element/2 will be │ │ │ produced, otherwise the instruction i_element/4 will be produced. │ │ │ The corresponding specific instructions are:

    i_fast_element xy j? I d
    │ │ │  i_element xy j? s d

    The i_fast_element/2 instruction is faster because the tuple is │ │ │ already an untagged integer. It also knows that the index is at least │ │ │ 1, so it does not have to test for that. The i_element/4 │ │ │ instruction will have to fetch the index from a register, test that it │ │ │ is an integer, and untag the integer.

    At the time of writing, all generators functions were defined in files │ │ │ named generators.tab in several directories (in the same directories │ │ │ as the predicates.tab files).

    It is outside the scope of this document to describe in detail how │ │ │ generator functions are written, but here is the implementation of │ │ │ -element():

    gen.element(Fail, Index, Tuple, Dst) {
    │ │ │ +element():

    gen.element(Fail, Index, Tuple, Dst) {
    │ │ │      BeamOp* op;
    │ │ │  
    │ │ │ -    $NewBeamOp(S, op);
    │ │ │ +    $NewBeamOp(S, op);
    │ │ │  
    │ │ │ -    if (Index.type == TAG_i && Index.val > 0 &&
    │ │ │ +    if (Index.type == TAG_i && Index.val > 0 &&
    │ │ │          Index.val <= ERTS_MAX_TUPLE_SIZE &&
    │ │ │ -        (Tuple.type == TAG_x || Tuple.type == TAG_y)) {
    │ │ │ -        $BeamOpNameArity(op, i_fast_element, 4);
    │ │ │ -        op->a[0] = Tuple;
    │ │ │ -        op->a[1] = Fail;
    │ │ │ -        op->a[2].type = TAG_u;
    │ │ │ -        op->a[2].val = Index.val;
    │ │ │ -        op->a[3] = Dst;
    │ │ │ -    } else {
    │ │ │ -        $BeamOpNameArity(op, i_element, 4);
    │ │ │ -        op->a[0] = Tuple;
    │ │ │ -        op->a[1] = Fail;
    │ │ │ -        op->a[2] = Index;
    │ │ │ -        op->a[3] = Dst;
    │ │ │ -    }
    │ │ │ +        (Tuple.type == TAG_x || Tuple.type == TAG_y)) {
    │ │ │ +        $BeamOpNameArity(op, i_fast_element, 4);
    │ │ │ +        op->a[0] = Tuple;
    │ │ │ +        op->a[1] = Fail;
    │ │ │ +        op->a[2].type = TAG_u;
    │ │ │ +        op->a[2].val = Index.val;
    │ │ │ +        op->a[3] = Dst;
    │ │ │ +    } else {
    │ │ │ +        $BeamOpNameArity(op, i_element, 4);
    │ │ │ +        op->a[0] = Tuple;
    │ │ │ +        op->a[1] = Fail;
    │ │ │ +        op->a[2] = Index;
    │ │ │ +        op->a[3] = Dst;
    │ │ │ +    }
    │ │ │  
    │ │ │      return op;
    │ │ │ -}

    The gen. prefix tells beam_makeops that this function is a │ │ │ +}

    The gen. prefix tells beam_makeops that this function is a │ │ │ generator. Without the prefix, it would have been interpreted as the │ │ │ implementation of an instruction (described in Defining the │ │ │ implementation).

    Generator functions have a magic variabled called S, which is a │ │ │ pointer to a state struct. In the example, S is used in the invocation │ │ │ of the NewBeamOp macro.

    │ │ │ │ │ │ │ │ │ @@ -814,473 +814,473 @@ │ │ │ msg_instrs.tab │ │ │ select_instrs.tab │ │ │ trace_instrs.tab

    There is also a file that only contains macro definitions:

    macros.tab

    The syntax of each file is similar to C code. In fact, most of │ │ │ the contents is C code, interspersed with macro invocations.

    To allow Emacs to auto-indent the code, each file starts with the │ │ │ following line:

    // -*- c -*-

    To avoid messing up the indentation, all comments are written │ │ │ as C++ style comments (//) instead of #. Note that a comment │ │ │ must start at the beginning of a line.

    The meat of an instruction definition file are macro definitions. │ │ │ -We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │ +We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │      $Dst = $Src;
    │ │ │ -}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ +}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ allowed), the opening curly bracket must be on the same line, and the │ │ │ finishing curly bracket must be at the beginning of a line. It is │ │ │ recommended that the macro body is properly indented.

    As a convention, the macro arguments in the head all start with an │ │ │ uppercase letter. In the body, the macro arguments can be expanded │ │ │ by preceding them with $.

    A macro definition whose name and arity matches a family of │ │ │ specific instructions is assumed to be the implementation of that │ │ │ instruction.

    A macro can also be invoked from within another macro. For example, │ │ │ move_deallocate_return/2 avoids repeating code by invoking │ │ │ -$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ -    x(0) = $Src;
    │ │ │ -    $deallocate_return($Deallocate);
    │ │ │ -}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │ +$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ +    x(0) = $Src;
    │ │ │ +    $deallocate_return($Deallocate);
    │ │ │ +}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │      //| -no_next
    │ │ │      int words_to_pop = $Deallocate;
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ -{
    │ │ │ -  x(0) = I[1];
    │ │ │ -  do {
    │ │ │ -    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ +{
    │ │ │ +  x(0) = I[1];
    │ │ │ +  do {
    │ │ │ +    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -  } while (0);
    │ │ │ -}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ + } while (0); │ │ │ +}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ do/while wrapper unless beam_makeops can clearly see that no │ │ │ wrapper is needed. In this case, the wrapper is needed.

    Note that arguments for macros cannot be complex expressions, because │ │ │ the arguments are split on ,. For example, the following would │ │ │ not work because beam_makeops would split the expression into │ │ │ -two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ +two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ specially. They will be copied to the file with the generated code │ │ │ along with the rest of code in the body.

    However, there is an exception. Within a macro definition, a line that │ │ │ starts with whitespace followed by //| is treated specially. The │ │ │ rest of the line is assumed to contain directives to control code │ │ │ generation.

    Currently, two code generation directives are recognized:

    The -no_prefetch directive

    To see what -no_prefetch does, let's first look at the default code │ │ │ -generation. Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +generation.  Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    Note that the very first thing done is to fetch the address to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    Note that the very first thing done is to fetch the address to the │ │ │ next instruction. The reason is that it usually improves performance.

    Just as a demonstration, we can add a -no_prefetch directive to │ │ │ -the move/2 instruction:

    move(Src, Dst) {
    │ │ │ +the move/2 instruction:

    move(Src, Dst) {
    │ │ │      //| -no_prefetch
    │ │ │      $Dst = $Src;
    │ │ │ -}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ -For example:

    is_atom(Fail, Src) {
    │ │ │ -    if (is_not_atom($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ +For example:

    is_atom(Fail, Src) {
    │ │ │ +    if (is_not_atom($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -FAIL(Fail) {
    │ │ │ +FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    is_atom/2 may either execute the next instruction (if the second │ │ │ -operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ -{
    │ │ │ -  if (is_not_atom(xb(I[1]))) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -    Goto(*I);;
    │ │ │ -  }
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    is_atom/2 may either execute the next instruction (if the second │ │ │ +operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ +{
    │ │ │ +  if (is_not_atom(xb(I[1]))) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +    Goto(*I);;
    │ │ │ +  }
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ -is the jump/1 instruction:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ +is the jump/1 instruction:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -JUMP(Fail) {
    │ │ │ +JUMP(Fail) {
    │ │ │      //| -no_next
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ -}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │ +}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    In the end, the C compiler will probably optimize this code to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    In the end, the C compiler will probably optimize this code to the │ │ │ same native code as the first version, but the first version is certainly │ │ │ much easier to read for human readers.

    Macros in the macros.tab file

    The file macros.tab contains many useful macros. When implementing │ │ │ new instructions it is good practice to look through macros.tab to │ │ │ see if any of existing macros can be used rather than re-inventing │ │ │ the wheel.

    We will describe a few of the most useful macros here.

    The GC_REGEXP definition

    The following line defines a regular expression that will recognize │ │ │ a call to a function that does a garbage collection:

     GC_REGEXP=erts_garbage_collect|erts_gc|GcBifFunction;

    The purpose is that beam_makeops can verify that an instruction │ │ │ that does a garbage collection and has an d operand uses the │ │ │ $REFRESH_GEN_DEST() macro.

    If you need to define a new function that does garbage collection, │ │ │ you should give it the prefix erts_gc_. If that is not possible │ │ │ you should update the regular expression so that it will match your │ │ │ -new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ -    if (is_not_list($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ -instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ +new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ +    if (is_not_list($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ +instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ stack space and heap space is available. If not it will do a │ │ │ -garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ -    $GC_TEST(0, $Nh, $Live);
    │ │ │ -}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ +garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ +    $GC_TEST(0, $Nh, $Live);
    │ │ │ +}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ optionally additional heap space.

    Pre-defined macros and variables

    beam_makeops defines several built-in macros and pre-bound variables.

    The NEXT_INSTRUCTION pre-bound variable

    The NEXT_INSTRUCTION is a pre-bound variable that is available in │ │ │ -all instructions. It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │ +all instructions.  It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │      //| -no_next
    │ │ │ -    $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
    │ │ │ -    $DISPATCH_REL($CallDest);
    │ │ │ -}

    When calling a function, the return address is first stored in E[0] │ │ │ + $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION); │ │ │ + $DISPATCH_REL($CallDest); │ │ │ +}

    When calling a function, the return address is first stored in E[0] │ │ │ (using the $SAVE_CONTINUATION_POINTER() macro), and then control is │ │ │ -transferred to the callee. Here is the generated code:

    OpCase(i_call_f):
    │ │ │ -{
    │ │ │ -    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ -    *E = (BeamInstr) (I+2);;
    │ │ │ +transferred to the callee.  Here is the generated code:

    OpCase(i_call_f):
    │ │ │ +{
    │ │ │ +    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ +    *E = (BeamInstr) (I+2);;
    │ │ │  
    │ │ │      /* ... dispatch code intentionally left out ... */
    │ │ │ -}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ +}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ That makes sense since the size of the i_call_f/1 instruction is │ │ │ two words.

    The IP_ADJUSTMENT pre-bound variable

    $IP_ADJUSTMENT is usually 0. In a few combined instructions │ │ │ (described below) it can be non-zero. It is used like this │ │ │ -in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │ +in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │      I += $Offset + $IP_ADJUSTMENT;
    │ │ │ -}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ +}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ one of the macros that invoke such as FAIL() or JUMP() │ │ │ defined in macros.tab.

    Pre-defined macro functions

    The IF() macro

    $IF(Expr, IfTrue, IfFalse) evaluates Expr, which must be a valid │ │ │ Perl expression (which for simple numeric expressions have the same │ │ │ syntax as C). If Expr evaluates to 0, the entire IF() expression will be │ │ │ replaced with IfFalse, otherwise it will be replaced with IfTrue.

    See the description of OPERAND_POSITION() for an example.

    The OPERAND_POSITION() macro

    $OPERAND_POSITION(Expr) returns the position for Expr, if │ │ │ Expr is an operand that is not packed. The first operand is │ │ │ -at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │ +at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │ +    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │          goto common_jump,
    │ │ │ -        $DO_JUMP($Fail));
    │ │ │ -}
    │ │ │ +        $DO_JUMP($Fail));
    │ │ │ +}
    │ │ │  
    │ │ │ -DO_JUMP(Fail) {
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I));
    │ │ │ -}
    │ │ │ +DO_JUMP(Fail) {
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I));
    │ │ │ +}
    │ │ │  
    │ │ │  // In beam_emu.c:
    │ │ │  common_jump:
    │ │ │ -   I += I[1];
    │ │ │ -   Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ + I += I[1]; │ │ │ + Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ of the instruction, a pointer will be initialized to point to the X or │ │ │ Y register in question.

    If there is a garbage collection before the result is stored, │ │ │ the stack will move and if the d operand referred to a Y │ │ │ register, the pointer will no longer be valid. (Y registers are │ │ │ stored on the stack.)

    In those circumstances, $REFRESH_GEN_DEST() must be invoked │ │ │ to set up the pointer again. beam_makeops will notice │ │ │ if there is a call to a function that does a garbage collection and │ │ │ $REFRESH_GEN_DEST() is not called.

    Here is a complete example. The new_map instruction is defined │ │ │ -like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │ +like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │      Eterm res;
    │ │ │  
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │ +    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    $REFRESH_GEN_DEST();
    │ │ │ +    $REFRESH_GEN_DEST();
    │ │ │      $Dst = res;
    │ │ │ -    $NEXT($NEXT_INSTRUCTION+$N);
    │ │ │ -}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ -similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ -... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ + $NEXT($NEXT_INSTRUCTION+$N); │ │ │ +}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ +similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ +... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ of operands for the interpreter. Here is the instruction definition in emu/ops.tab:

    put_tuple2 xy I *

    For the interpreter, the * is optional, because it does not effect code generation │ │ │ in any way. However, it is recommended to include it to make it clear for human readers │ │ │ that there is a variable number of operands.

    Use the $NEXT_INSTRUCTION macro to obtain a pointer to the first of the variable │ │ │ -operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │ +operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │  Eterm* hp = HTOP;
    │ │ │  Eterm arity = $Arity;
    │ │ │ -Eterm* dst_ptr = &($Dst);
    │ │ │ +Eterm* dst_ptr = &($Dst);
    │ │ │  
    │ │ │  //| -no_next
    │ │ │ -ASSERT(arity != 0);
    │ │ │ -*hp++ = make_arityval(arity);
    │ │ │ +ASSERT(arity != 0);
    │ │ │ +*hp++ = make_arityval(arity);
    │ │ │  
    │ │ │  /*
    │ │ │   * The $NEXT_INSTRUCTION macro points just beyond the fixed
    │ │ │   * operands. In this case it points to the descriptor of
    │ │ │   * the first element to be put into the tuple.
    │ │ │   */
    │ │ │  I = $NEXT_INSTRUCTION;
    │ │ │ -do {
    │ │ │ +do {
    │ │ │      Eterm term = *I++;
    │ │ │ -    switch (loader_tag(term)) {
    │ │ │ +    switch (loader_tag(term)) {
    │ │ │      case LOADER_X_REG:
    │ │ │ -    *hp++ = x(loader_x_reg_index(term));
    │ │ │ +    *hp++ = x(loader_x_reg_index(term));
    │ │ │      break;
    │ │ │      case LOADER_Y_REG:
    │ │ │ -    *hp++ = y(loader_y_reg_index(term));
    │ │ │ +    *hp++ = y(loader_y_reg_index(term));
    │ │ │      break;
    │ │ │      default:
    │ │ │      *hp++ = term;
    │ │ │      break;
    │ │ │ -    }
    │ │ │ -} while (--arity != 0);
    │ │ │ -*dst_ptr = make_tuple(HTOP);
    │ │ │ +    }
    │ │ │ +} while (--arity != 0);
    │ │ │ +*dst_ptr = make_tuple(HTOP);
    │ │ │  HTOP = hp;
    │ │ │ -ASSERT(VALID_INSTR(* (Eterm *)I));
    │ │ │ -Goto(*I);
    │ │ │ -}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ +ASSERT(VALID_INSTR(* (Eterm *)I)); │ │ │ +Goto(*I); │ │ │ +}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ "fast" operands types such as x and y, as opposed to s or S. │ │ │ To avoid an explosion in code size, we want to share most of the │ │ │ implementation between the instructions. Here are the specific │ │ │ instructions for i_increment/5:

    i_increment r W t d
    │ │ │  i_increment x W t d
    │ │ │ -i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │ +i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_reg_source = $Source;
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    There will be three almost identical copies of the code. Given the │ │ │ +}

    There will be three almost identical copies of the code. Given the │ │ │ size of the code, that could be too high cost to pay.

    To avoid the three copies of the code, we could use only one specific │ │ │ instruction:

    i_increment S W t d

    (The same implementation as above will work.)

    That reduces the code size, but is slower because S means that │ │ │ there will be extra code to test whether the operand refers to an X │ │ │ register or a Y register.

    Solution: We can use "combined instructions". Combined │ │ │ instructions are combined from instruction fragments. The │ │ │ bulk of the code can be shared.

    Here we will show how i_increment can be implemented as a combined │ │ │ instruction. We will show each individual fragment first, and then │ │ │ show how to connect them together. First we will need a variable that │ │ │ -we can store the value fetched from the register in:

    increment.head() {
    │ │ │ +we can store the value fetched from the register in:

    increment.head() {
    │ │ │      Eterm increment_reg_val;
    │ │ │ -}

    The name increment is the name of the group that the fragment │ │ │ +}

    The name increment is the name of the group that the fragment │ │ │ belongs to. Note that it does not need to have the same │ │ │ name as the instruction. The group name is followed by . and │ │ │ the name of the fragment. The name head is pre-defined. │ │ │ The code in it will be placed at the beginning of a block, so │ │ │ that all fragments in the group can access it.

    Next we define the fragment that will pick up the value from the │ │ │ -register from the first operand:

    increment.fetch(Src) {
    │ │ │ +register from the first operand:

    increment.fetch(Src) {
    │ │ │      increment_reg_val = $Src;
    │ │ │ -}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ -times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │ +}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ +times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    We call this fragment execute. It will handle the three remaining │ │ │ +}

    We call this fragment execute. It will handle the three remaining │ │ │ operands (W t d). There will only be one copy of this fragment.

    Now that we have defined the fragments, we need to inform │ │ │ beam_makeops how they should be connected:

    i_increment := increment.fetch.execute;

    To the left of the := is the name of the specific instruction that │ │ │ should be implemented by the fragments, in this case i_increment. │ │ │ To the right of := is the name of the group with the fragments, │ │ │ followed by a .. Then the name of the fragments in the group are │ │ │ listed in the order they should be executed. Note that the head │ │ │ fragment is not listed.

    The line ends in ; (to avoid messing up the indentation in Emacs).

    (Note that in practice the := line is usually placed before the │ │ │ -fragments.)

    The generated code looks like this:

    {
    │ │ │ +fragments.)

    The generated code looks like this:

    {
    │ │ │    Eterm increment_reg_val;
    │ │ │ -  OpCase(i_increment_rWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = r(0);
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_rWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = r(0);
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_xWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_xWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_yWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_yWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │    increment__execute:
    │ │ │ -  {
    │ │ │ -    // Here follows the code from increment.execute()
    │ │ │ +  {
    │ │ │ +    // Here follows the code from increment.execute()
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}
    Some notes about combined instructions

    The operands that are different must be at │ │ │ +}

    Some notes about combined instructions

    The operands that are different must be at │ │ │ the beginning of the instruction. All operands in the last │ │ │ fragment must have the same operands in all variants of │ │ │ the specific instruction.

    As an example, the following specific instructions cannot be │ │ │ implemented as a combined instruction:

    i_times j? t x x d
    │ │ │  i_times j? t x y d
    │ │ │  i_times j? t s s d

    We would have to change the order of the operands so that the │ │ │ two operands that are different are placed first:

    i_times x x j? t d
    │ │ │  i_times x y j? t d
    │ │ │  i_times s s j? t d

    We can then define:

    i_times := times.fetch.execute;
    │ │ │  
    │ │ │ -times.head {
    │ │ │ +times.head {
    │ │ │      Eterm op1, op2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.fetch(Src1, Src2) {
    │ │ │ +times.fetch(Src1, Src2) {
    │ │ │      op1 = $Src1;
    │ │ │      op2 = $Src2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.execute(Fail, Live, Dst) {
    │ │ │ +times.execute(Fail, Live, Dst) {
    │ │ │      // Multiply op1 and op2.
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Several instructions can share a group. As an example, the following │ │ │ +}

    Several instructions can share a group. As an example, the following │ │ │ instructions have different names, but in the end they all create a │ │ │ binary. The last two operands are common for all of them:

    i_bs_init_fail       xy j? t? x
    │ │ │  i_bs_init_fail_heap s I j? t? x
    │ │ │  i_bs_init                W t? x
    │ │ │  i_bs_init_heap         W I t? x

    The instructions are defined like this (formatted with extra │ │ │ spaces for clarity):

    i_bs_init_fail_heap := bs_init . fail_heap . verify . execute;
    │ │ │  i_bs_init_fail      := bs_init . fail      . verify . execute;
    │ │ │  i_bs_init           := bs_init .           .  plain . execute;
    │ │ │  i_bs_init_heap      := bs_init .               heap . execute;

    Note that the first two instruction have three fragments, while the │ │ │ -other two only have two fragments. Here are the fragments:

    bs_init_bits.head() {
    │ │ │ +other two only have two fragments.  Here are the fragments:

    bs_init_bits.head() {
    │ │ │      Eterm num_bits_term;
    │ │ │      Uint num_bits;
    │ │ │      Uint alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.plain(NumBits) {
    │ │ │ +bs_init_bits.plain(NumBits) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.heap(NumBits, Alloc) {
    │ │ │ +bs_init_bits.heap(NumBits, Alloc) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail(NumBitsTerm) {
    │ │ │ +bs_init_bits.fail(NumBitsTerm) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │ +bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.verify(Fail) {
    │ │ │ +bs_init_bits.verify(Fail) {
    │ │ │      // Verify the num_bits_term, fail using $FAIL
    │ │ │      // if there is a problem.
    │ │ │  .
    │ │ │  .
    │ │ │  .
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.execute(Live, Dst) {
    │ │ │ +bs_init_bits.execute(Live, Dst) {
    │ │ │     // Long complicated code to a create a binary.
    │ │ │     .
    │ │ │     .
    │ │ │     .
    │ │ │ -}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ +}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ The generated code can be found in beam_warm.h.

    │ │ │ │ │ │ │ │ │ │ │ │ Code generation for BeamAsm │ │ │

    │ │ │

    For the BeamAsm runtime system, the implementation of each instruction is defined │ │ │ by emitter functions written in C++ that emit the assembly code for each instruction. │ │ │ There is one emitter function for each family of specific instructions.

    Take for example the move instruction. In beam/asm/ops.tab there is a │ │ │ -single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ -    mov_arg(Dst, Src);
    │ │ │ -}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ -operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ -mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ +single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +    mov_arg(Dst, Src);
    │ │ │ +}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ +operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ +mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ only one emitter function. For example:

    fload S l
    │ │ │  fload q l

    By defining fload like this, the source operand must be a X register, Y register, or │ │ │ a literal. If not, the loading will be aborted. If the instruction instead had been │ │ │ defined like this:

    fload s l

    attempting to load an invalid instruction such as {fload,{atom,clearly_bad},{fr,0}} │ │ │ would cause a crash (either at load time or when the instruction was executed).

    Regardless on how many specific instructions there are in the family, │ │ │ -only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ +}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ of operands could be handled. One such instructions is │ │ │ -select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ +instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ like this:

    select_val s f I *

    The * as the last operand will make sure that the variable operands │ │ │ are passed in as a Span of ArgVal (will be std::span in C++20 onwards). │ │ │ -Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │ +Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │                                            const ArgVal &Fail,
    │ │ │                                            const ArgVal &Size,
    │ │ │ -                                          const Span<ArgVal> &args) {
    │ │ │ -    ASSERT(Size.getValue() == args.size());
    │ │ │ +                                          const Span<ArgVal> &args) {
    │ │ │ +    ASSERT(Size.getValue() == args.size());
    │ │ │         .
    │ │ │         .
    │ │ │         .
    │ │ │ -}
    │ │ │ +
    }
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    beam_makeops. The transformations │ │ │ used in BeamAsm are much simpler than the interpreter's, as most of the │ │ │ transformations for the interpreter are done only to eliminate the instruction │ │ │ dispatch overhead.

    Then each instruction is encoded using the C++ functions in the │ │ │ -jit/$ARCH/instr_*.cpp files. For example:

    void BeamModuleAssembler::emit_is_nonempty_list(const ArgVal &Fail, const ArgVal &Src) {
    │ │ │ -  a.test(getArgRef(Src), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
    │ │ │ -  a.jne(labels[Fail.getLabel()]);
    │ │ │ -}

    asmjit provides a fairly straightforward │ │ │ +jit/$ARCH/instr_*.cpp files. For example:

    void BeamModuleAssembler::emit_is_nonempty_list(const ArgVal &Fail, const ArgVal &Src) {
    │ │ │ +  a.test(getArgRef(Src), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
    │ │ │ +  a.jne(labels[Fail.getLabel()]);
    │ │ │ +}

    asmjit provides a fairly straightforward │ │ │ mapping from a C++ function call to the x86 assembly instruction. The above │ │ │ instruction tests if the value in the Src register is a non-empty list and if │ │ │ it is not then it jumps to the fail label.

    For comparison, the interpreter has 8 combinations and specializations of │ │ │ this implementation to minimize the instruction dispatch overhead for │ │ │ common patterns.

    The original register allocation done by the Erlang compiler is used to manage the │ │ │ liveness of values and the physical registers are statically allocated to keep │ │ │ the necessary process state. At the moment this is the static register │ │ │ -allocation on x86-64:

    rbx: ErtsSchedulerRegisters struct (contains x/float registers and some metadata)
    │ │ │ +allocation on x86-64:

    rbx: ErtsSchedulerRegisters struct (contains x/float registers and some metadata)
    │ │ │  rbp: Current frame pointer when `perf` support is enabled, otherwise this
    │ │ │       is an optional save slot for the Erlang stack pointer when executing C
    │ │ │       code.
    │ │ │  r12: Active code index
    │ │ │  r13: Current running process
    │ │ │  r14: Remaining reductions
    │ │ │  r15: Erlang heap pointer

    Note that all of these are callee save registers under the System V and Windows │ │ │ @@ -183,21 +183,21 @@ │ │ │ shared and only the arguments to the instructions vary. Using as little memory as │ │ │ possible has many advantages; less memory is used, loading time decreases, │ │ │ higher cache hit-rate.

    In BeamAsm we need to achieve something similar since the load-time of a module │ │ │ scales almost linearly with the amount of memory it uses. Early BeamAsm prototypes │ │ │ used about double the amount of memory for code as the interpreter, while current │ │ │ versions use about 10% more. How was this achieved?

    In BeamAsm we heavily use shared code fragments to try to emit as much code as │ │ │ possible as global shared fragments instead of duplicating the code unnecessarily. │ │ │ -For instance, the return instruction looks something like this:

    Label yield = a.newLabel();
    │ │ │ +For instance, the return instruction looks something like this:

    Label yield = a.newLabel();
    │ │ │  
    │ │ │  /* Decrement reduction counter */
    │ │ │ -a.dec(FCALLS);
    │ │ │ +a.dec(FCALLS);
    │ │ │  /* If FCALLS < 0, jump to the yield-on-return fragment */
    │ │ │ -a.jl(resolve_fragment(ga->get_dispatch_return()));
    │ │ │ -a.ret();

    The code above is not exactly what is emitted, but close enough. The thing to note │ │ │ +a.jl(resolve_fragment(ga->get_dispatch_return())); │ │ │ +a.ret();

    The code above is not exactly what is emitted, but close enough. The thing to note │ │ │ is that the code for doing the context switch is never emitted. Instead, we jump │ │ │ to a global fragment that all return instructions share. This greatly reduces │ │ │ the amount of code that has to be emitted for each module.

    │ │ │ │ │ │ │ │ │ │ │ │ Running Erlang code │ │ │ @@ -239,43 +239,43 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running C code │ │ │

    │ │ │

    As Erlang stacks can be very small, we have to switch over to a different stack │ │ │ when we need to execute C code (which may expect a much larger stack). This is │ │ │ -done through emit_enter_runtime and emit_leave_runtime, for example:

    mov_arg(ARG4, NumFree);
    │ │ │ +done through emit_enter_runtime and emit_leave_runtime, for example:

    mov_arg(ARG4, NumFree);
    │ │ │  
    │ │ │  /* Move to the C stack and swap out our current reductions, stack-, and
    │ │ │   * heap pointer to the process structure. */
    │ │ │ -emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │ +emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │  
    │ │ │ -a.mov(ARG1, c_p);
    │ │ │ -load_x_reg_array(ARG2);
    │ │ │ -make_move_patch(ARG3, lambdas[Fun.getValue()].patches);
    │ │ │ +a.mov(ARG1, c_p);
    │ │ │ +load_x_reg_array(ARG2);
    │ │ │ +make_move_patch(ARG3, lambdas[Fun.getValue()].patches);
    │ │ │  
    │ │ │  /* Call `new_fun`, asserting that we're on the C stack. */
    │ │ │ -runtime_call<4>(new_fun);
    │ │ │ +runtime_call<4>(new_fun);
    │ │ │  
    │ │ │  /* Move back to the C stack, and read the updated values from the process
    │ │ │   * structure */
    │ │ │ -emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │ +emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │  
    │ │ │ -a.mov(getXRef(0), RET);

    All combinations of the Update constants are legal, but the ones given to │ │ │ +a.mov(getXRef(0), RET);

    All combinations of the Update constants are legal, but the ones given to │ │ │ emit_leave_runtime must be the same as those given to emit_enter_runtime.

    │ │ │ │ │ │ │ │ │ │ │ │ Tracing and NIF Loading │ │ │

    │ │ │

    To make tracing and NIF loading work there needs to be a way to intercept │ │ │ any function call. In the interpreter, this is done by rewriting the loaded │ │ │ BEAM code, but this is more complicated in BeamAsm as we want to have a fast │ │ │ and compact way to do this. This is solved by emitting the code below at the │ │ │ -start of each function (x86 variant below):

      0x0: short jmp 6 (address 0x8)
    │ │ │ +start of each function (x86 variant below):

      0x0: short jmp 6 (address 0x8)
    │ │ │    0x2: nop
    │ │ │    0x3: relative near call to shared breakpoint fragment
    │ │ │    0x8: actual code for function

    When code starts to execute it will simply see the short jmp 6 instruction │ │ │ which skips the prologue and starts to execute the code directly.

    When we want to enable a certain breakpoint we set the jmp target to be 1, │ │ │ which means it will land on the call to the shared breakpoint fragment. This │ │ │ fragment checks the current breakpoint_flag stored in the ErtsCodeInfo of │ │ │ this function, and then calls erts_call_nif_early and │ │ │ @@ -289,31 +289,31 @@ │ │ │ Updating code │ │ │ │ │ │

    Because many environments enforce W^X it's not always possible to write │ │ │ directly to the code pages. Because of this we map code twice: once with an │ │ │ executable page and once with a writable page. Since they're backed by the │ │ │ same memory, writes to the writable page appear magically in the executable │ │ │ one.

    The erts_writable_code_ptr function can be used to get writable pointers │ │ │ -given a module instance, provided that it has been unsealed first:

    for (i = 0; i < n; i++) {
    │ │ │ +given a module instance, provided that it has been unsealed first:

    for (i = 0; i < n; i++) {
    │ │ │      const ErtsCodeInfo* ci_exec;
    │ │ │      ErtsCodeInfo* ci_rw;
    │ │ │      void *w_ptr;
    │ │ │  
    │ │ │ -    erts_unseal_module(&modp->curr);
    │ │ │ +    erts_unseal_module(&modp->curr);
    │ │ │  
    │ │ │ -    ci_exec = code_hdr->functions[i];
    │ │ │ -    w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
    │ │ │ -    ci_rw = (ErtsCodeInfo*)w_ptr;
    │ │ │ +    ci_exec = code_hdr->functions[i];
    │ │ │ +    w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
    │ │ │ +    ci_rw = (ErtsCodeInfo*)w_ptr;
    │ │ │  
    │ │ │ -    uninstall_breakpoint(ci_rw, ci_exec);
    │ │ │ -    consolidate_bp_data(modp, ci_rw, 1);
    │ │ │ -    ASSERT(ci_rw->gen_bp == NULL);
    │ │ │ +    uninstall_breakpoint(ci_rw, ci_exec);
    │ │ │ +    consolidate_bp_data(modp, ci_rw, 1);
    │ │ │ +    ASSERT(ci_rw->gen_bp == NULL);
    │ │ │  
    │ │ │ -    erts_seal_module(&modp->curr);
    │ │ │ -}

    Without the module instance there's no reliable way to figure out the writable │ │ │ + erts_seal_module(&modp->curr); │ │ │ +}

    Without the module instance there's no reliable way to figure out the writable │ │ │ address of a code page, and we rely on address space layout randomization │ │ │ (ASLR) to make it difficult to guess. On some platforms, security is further │ │ │ enhanced by protecting the writable area from writes until the module has been │ │ │ unsealed by erts_unseal_module.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -393,15 +393,15 @@ │ │ │ perf script > out.perf │ │ │ ## run stackcollapse │ │ │ stackcollapse-perf.pl out.perf > out.folded │ │ │ ## Create the svg │ │ │ flamegraph.pl out.folded > out.svg

    We get a graph that would look something like this:

    Linux Perf FlameGraph: dialyzer PLT build

    You can view a larger version here. It contains │ │ │ the same information, but it is easier to share with others as it does │ │ │ not need the symbols in the executable.

    Using the same data we can also produce a graph where the scheduler profile data │ │ │ -has been merged by using sed:

    ## Strip [0-9]+_ and/or _[0-9]+ from all scheduler names
    │ │ │ +has been merged by using sed:

    ## Strip [0-9]+_ and/or _[0-9]+ from all scheduler names
    │ │ │  ## scheduler names changed in OTP26, hence two expressions
    │ │ │  sed -e 's/^[0-9]\+_//' -e 's/^erts_\([^_]\+\)_[0-9]\+/erts_\1/' out.folded > out.folded_sched
    │ │ │  ## Create the svg
    │ │ │  flamegraph.pl out.folded_sched > out_sched.svg

    Linux Perf FlameGraph: dialyzer PLT build

    You can view a larger version here. │ │ │ There are many different transformations that you can do to make the graph show │ │ │ you what you want.

    │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/codeloading.html │ │ │ @@ -163,16 +163,16 @@ │ │ │ only be done by one loader process at a time. A second loader process │ │ │ trying to enter finishing phase will be suspended until the first │ │ │ loader is done. This will only block the process, the scheduler is │ │ │ free to schedule other work while the second loader is waiting. (See │ │ │ erts_try_seize_code_load_permission and │ │ │ erts_release_code_load_permission).

    The ability to prepare several modules in parallel is not currently │ │ │ used as almost all code loading is serialized by the code_server │ │ │ -process. The BIF interface is however prepared for this.

      erlang:prepare_loading(Module, Code) -> LoaderState
    │ │ │ -  erlang:finish_loading([LoaderState])

    The idea is that prepare_loading could be called in parallel for │ │ │ +process. The BIF interface is however prepared for this.

      erlang:prepare_loading(Module, Code) -> LoaderState
    │ │ │ +  erlang:finish_loading([LoaderState])

    The idea is that prepare_loading could be called in parallel for │ │ │ different modules and returns a "magic binary" containing the internal │ │ │ state of each prepared module. Function finish_loading could take a │ │ │ list of such states and do the finishing of all of them in one go.

    Currently we use the legacy BIF erlang:load_module which is now │ │ │ implemented in Erlang by calling the above two functions in │ │ │ sequence. Function finish_loading is limited to only accepts a list │ │ │ with one module state as we do not yet use the multi module loading │ │ │ feature.

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/crash_dump.html │ │ │ @@ -401,21 +401,21 @@ │ │ │ put/2 and get/1 thing) is non-empty.

    The raw memory information can be decoded by the Crashdump Viewer tool. You can │ │ │ then see the stack dump, the message queue (if any), and the dictionary (if │ │ │ any).

    The stack dump is a dump of the Erlang process stack. Most of the live data │ │ │ (that is, variables currently in use) are placed on the stack; thus this can be │ │ │ interesting. One has to "guess" what is what, but as the information is │ │ │ symbolic, thorough reading of this information can be useful. As an example, we │ │ │ can find the state variable of the Erlang primitive loader online (5) and │ │ │ -(6) in the following example:

    (1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
    │ │ │ -(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
    │ │ │ -(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
    │ │ │ -(4)  y(1)     <0.1.0>
    │ │ │ -(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
    │ │ │ -(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
    │ │ │ -(7)  y(3)     infinity

    When interpreting the data for a process, it is helpful to know that anonymous │ │ │ +(6) in the following example:

    (1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
    │ │ │ +(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
    │ │ │ +(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
    │ │ │ +(4)  y(1)     <0.1.0>
    │ │ │ +(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
    │ │ │ +(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
    │ │ │ +(7)  y(3)     infinity

    When interpreting the data for a process, it is helpful to know that anonymous │ │ │ function objects (funs) are given the following:

    • A name constructed from the name of the function in which they are created
    • A number (starting with 0) indicating the number of that fun within that │ │ │ function

    │ │ │ │ │ │ │ │ │ │ │ │ Atoms │ │ │

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/driver.html │ │ │ @@ -364,41 +364,41 @@ │ │ │

    Before a driver can be called from Erlang, it must be loaded and opened. Loading │ │ │ is done using the erl_ddll module (the erl_ddll driver that loads dynamic │ │ │ driver is actually a driver itself). If loading is successful, the port can be │ │ │ opened with open_port/2. The port name must match the name of │ │ │ the shared library and the name in the driver entry structure.

    When the port has been opened, the driver can be called. In the pg_sync │ │ │ example, we do not have any data from the port, only the return value from the │ │ │ port_control/3.

    The following code is the Erlang part of the synchronous postgres driver, │ │ │ -pg_sync.erl:

    -module(pg_sync).
    │ │ │ +pg_sync.erl:

    -module(pg_sync).
    │ │ │  
    │ │ │ --define(DRV_CONNECT, 1).
    │ │ │ --define(DRV_DISCONNECT, 2).
    │ │ │ --define(DRV_SELECT, 3).
    │ │ │ +-define(DRV_CONNECT, 1).
    │ │ │ +-define(DRV_DISCONNECT, 2).
    │ │ │ +-define(DRV_SELECT, 3).
    │ │ │  
    │ │ │ --export([connect/1, disconnect/1, select/2]).
    │ │ │ +-export([connect/1, disconnect/1, select/2]).
    │ │ │  
    │ │ │ -connect(ConnectStr) ->
    │ │ │ -    case erl_ddll:load_driver(".", "pg_sync") of
    │ │ │ +connect(ConnectStr) ->
    │ │ │ +    case erl_ddll:load_driver(".", "pg_sync") of
    │ │ │          ok -> ok;
    │ │ │ -        {error, already_loaded} -> ok;
    │ │ │ -        E -> exit({error, E})
    │ │ │ +        {error, already_loaded} -> ok;
    │ │ │ +        E -> exit({error, E})
    │ │ │      end,
    │ │ │ -    Port = open_port({spawn, ?MODULE}, []),
    │ │ │ -    case binary_to_term(port_control(Port, ?DRV_CONNECT, ConnectStr)) of
    │ │ │ -        ok -> {ok, Port};
    │ │ │ +    Port = open_port({spawn, ?MODULE}, []),
    │ │ │ +    case binary_to_term(port_control(Port, ?DRV_CONNECT, ConnectStr)) of
    │ │ │ +        ok -> {ok, Port};
    │ │ │          Error -> Error
    │ │ │      end.
    │ │ │  
    │ │ │ -disconnect(Port) ->
    │ │ │ -    R = binary_to_term(port_control(Port, ?DRV_DISCONNECT, "")),
    │ │ │ -    port_close(Port),
    │ │ │ +disconnect(Port) ->
    │ │ │ +    R = binary_to_term(port_control(Port, ?DRV_DISCONNECT, "")),
    │ │ │ +    port_close(Port),
    │ │ │      R.
    │ │ │  
    │ │ │ -select(Port, Query) ->
    │ │ │ -    binary_to_term(port_control(Port, ?DRV_SELECT, Query)).

    The API is simple:

    • connect/1 loads the driver, opens it, and logs on to the database, returning │ │ │ +select(Port, Query) -> │ │ │ + binary_to_term(port_control(Port, ?DRV_SELECT, Query)).

    The API is simple:

    • connect/1 loads the driver, opens it, and logs on to the database, returning │ │ │ the Erlang port if successful.
    • select/2 sends a query to the driver and returns the result.
    • disconnect/1 closes the database connection and the driver. (However, it │ │ │ does not unload it.)

    The connection string is to be a connection string for postgres.

    The driver is loaded with erl_ddll:load_driver/2. If this is successful, or if │ │ │ it is already loaded, it is opened. This will call the start function in the │ │ │ driver.

    We use the port_control/3 function for all calls into the │ │ │ driver. The result from the driver is returned immediately and converted to │ │ │ terms by calling binary_to_term/1. (We trust that the │ │ │ terms returned from the driver are well-formed, otherwise the binary_to_term/1 │ │ │ @@ -536,51 +536,51 @@ │ │ │ successful, or error if it is not. If the connection is not yet established, we │ │ │ simply return; ready_io is called again.

    If we have a result from a connect, indicated by having data in the x buffer, │ │ │ we no longer need to select on output (ready_output), so we remove this by │ │ │ calling driver_select.

    If we are not connecting, we wait for results from a PQsendQuery, so we get │ │ │ the result and return it. The encoding is done with the same functions as in the │ │ │ earlier example.

    Error handling is to be added here, for example, checking that the socket is │ │ │ still open, but this is only a simple example.

    The Erlang part of the asynchronous driver consists of the sample file │ │ │ -pg_async.erl.

    -module(pg_async).
    │ │ │ +pg_async.erl.

    -module(pg_async).
    │ │ │  
    │ │ │ --define(DRV_CONNECT, $C).
    │ │ │ --define(DRV_DISCONNECT, $D).
    │ │ │ --define(DRV_SELECT, $S).
    │ │ │ +-define(DRV_CONNECT, $C).
    │ │ │ +-define(DRV_DISCONNECT, $D).
    │ │ │ +-define(DRV_SELECT, $S).
    │ │ │  
    │ │ │ --export([connect/1, disconnect/1, select/2]).
    │ │ │ +-export([connect/1, disconnect/1, select/2]).
    │ │ │  
    │ │ │ -connect(ConnectStr) ->
    │ │ │ -    case erl_ddll:load_driver(".", "pg_async") of
    │ │ │ +connect(ConnectStr) ->
    │ │ │ +    case erl_ddll:load_driver(".", "pg_async") of
    │ │ │          ok -> ok;
    │ │ │ -        {error, already_loaded} -> ok;
    │ │ │ -        _ -> exit({error, could_not_load_driver})
    │ │ │ +        {error, already_loaded} -> ok;
    │ │ │ +        _ -> exit({error, could_not_load_driver})
    │ │ │      end,
    │ │ │ -    Port = open_port({spawn, ?MODULE}, [binary]),
    │ │ │ -    port_control(Port, ?DRV_CONNECT, ConnectStr),
    │ │ │ -    case return_port_data(Port) of
    │ │ │ +    Port = open_port({spawn, ?MODULE}, [binary]),
    │ │ │ +    port_control(Port, ?DRV_CONNECT, ConnectStr),
    │ │ │ +    case return_port_data(Port) of
    │ │ │          ok ->
    │ │ │ -            {ok, Port};
    │ │ │ +            {ok, Port};
    │ │ │          Error ->
    │ │ │              Error
    │ │ │      end.
    │ │ │  
    │ │ │ -disconnect(Port) ->
    │ │ │ -    port_control(Port, ?DRV_DISCONNECT, ""),
    │ │ │ -    R = return_port_data(Port),
    │ │ │ -    port_close(Port),
    │ │ │ +disconnect(Port) ->
    │ │ │ +    port_control(Port, ?DRV_DISCONNECT, ""),
    │ │ │ +    R = return_port_data(Port),
    │ │ │ +    port_close(Port),
    │ │ │      R.
    │ │ │  
    │ │ │ -select(Port, Query) ->
    │ │ │ -    port_control(Port, ?DRV_SELECT, Query),
    │ │ │ -    return_port_data(Port).
    │ │ │ +select(Port, Query) ->
    │ │ │ +    port_control(Port, ?DRV_SELECT, Query),
    │ │ │ +    return_port_data(Port).
    │ │ │  
    │ │ │ -return_port_data(Port) ->
    │ │ │ +return_port_data(Port) ->
    │ │ │      receive
    │ │ │ -        {Port, {data, Data}} ->
    │ │ │ -            binary_to_term(Data)
    │ │ │ +        {Port, {data, Data}} ->
    │ │ │ +            binary_to_term(Data)
    │ │ │      end.

    The Erlang code is slightly different, as we do not return the result │ │ │ synchronously from port_control/3, instead we get it from driver_output as │ │ │ data in the message queue. The function return_port_data above receives data │ │ │ from the port. As the data is in binary format, we use │ │ │ binary_to_term/1 to convert it to an Erlang term. Notice │ │ │ that the driver is opened in binary mode (open_port/2 is │ │ │ called with option [binary]). This means that data sent from the driver to the │ │ │ @@ -677,59 +677,59 @@ │ │ │ *rp++ = ERL_DRV_LIST; │ │ │ *rp++ = n+1; │ │ │ driver_output_term(port, result, result_n); │ │ │ delete[] result; │ │ │ delete d; │ │ │ }

    This driver is called like the others from Erlang. However, as we use │ │ │ driver_output_term, there is no need to call binary_to_term/1. The Erlang code │ │ │ -is in the sample file next_perm.erl.

    The input is changed into a list of integers and sent to the driver.

    -module(next_perm).
    │ │ │ +is in the sample file next_perm.erl.

    The input is changed into a list of integers and sent to the driver.

    -module(next_perm).
    │ │ │  
    │ │ │ --export([next_perm/1, prev_perm/1, load/0, all_perm/1]).
    │ │ │ +-export([next_perm/1, prev_perm/1, load/0, all_perm/1]).
    │ │ │  
    │ │ │ -load() ->
    │ │ │ -    case whereis(next_perm) of
    │ │ │ +load() ->
    │ │ │ +    case whereis(next_perm) of
    │ │ │          undefined ->
    │ │ │ -            case erl_ddll:load_driver(".", "next_perm") of
    │ │ │ +            case erl_ddll:load_driver(".", "next_perm") of
    │ │ │                  ok -> ok;
    │ │ │ -                {error, already_loaded} -> ok;
    │ │ │ -                E -> exit(E)
    │ │ │ +                {error, already_loaded} -> ok;
    │ │ │ +                E -> exit(E)
    │ │ │              end,
    │ │ │ -            Port = open_port({spawn, "next_perm"}, []),
    │ │ │ -            register(next_perm, Port);
    │ │ │ +            Port = open_port({spawn, "next_perm"}, []),
    │ │ │ +            register(next_perm, Port);
    │ │ │          _ ->
    │ │ │              ok
    │ │ │      end.
    │ │ │  
    │ │ │ -list_to_integer_binaries(L) ->
    │ │ │ -    [<<I:32/integer-native>> || I <- L].
    │ │ │ +list_to_integer_binaries(L) ->
    │ │ │ +    [<<I:32/integer-native>> || I <- L].
    │ │ │  
    │ │ │ -next_perm(L) ->
    │ │ │ -    next_perm(L, 1).
    │ │ │ +next_perm(L) ->
    │ │ │ +    next_perm(L, 1).
    │ │ │  
    │ │ │ -prev_perm(L) ->
    │ │ │ -    next_perm(L, 2).
    │ │ │ +prev_perm(L) ->
    │ │ │ +    next_perm(L, 2).
    │ │ │  
    │ │ │ -next_perm(L, Nxt) ->
    │ │ │ -    load(),
    │ │ │ -    B = list_to_integer_binaries(L),
    │ │ │ -    port_control(next_perm, Nxt, B),
    │ │ │ +next_perm(L, Nxt) ->
    │ │ │ +    load(),
    │ │ │ +    B = list_to_integer_binaries(L),
    │ │ │ +    port_control(next_perm, Nxt, B),
    │ │ │      receive
    │ │ │          Result ->
    │ │ │              Result
    │ │ │      end.
    │ │ │  
    │ │ │ -all_perm(L) ->
    │ │ │ -    New = prev_perm(L),
    │ │ │ -    all_perm(New, L, [New]).
    │ │ │ +all_perm(L) ->
    │ │ │ +    New = prev_perm(L),
    │ │ │ +    all_perm(New, L, [New]).
    │ │ │  
    │ │ │ -all_perm(L, L, Acc) ->
    │ │ │ +all_perm(L, L, Acc) ->
    │ │ │      Acc;
    │ │ │ -all_perm(L, Orig, Acc) ->
    │ │ │ -    New = prev_perm(L),
    │ │ │ -    all_perm(New, Orig, [New | Acc]).
    │ │ │ +
    all_perm(L, Orig, Acc) -> │ │ │ + New = prev_perm(L), │ │ │ + all_perm(New, Orig, [New | Acc]).
    │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ init.

    The init process itself interprets some of these flags, the init flags. It │ │ │ also stores any remaining flags, the user flags. The latter can be retrieved │ │ │ by calling init:get_argument/1.

    A small number of "-" flags exist, which now actually are emulator flags, see │ │ │ the description below.

  • Plain arguments are not interpreted in any way. They are also stored by the │ │ │ init process and can be retrieved by calling init:get_plain_arguments/0. │ │ │ Plain arguments can occur before the first flag, or after a -- flag. Also, │ │ │ the -extra flag causes everything that follows to become plain arguments.

  • Examples:

    % erl +W w -sname arnie +R 9 -s my_init -extra +bertie
    │ │ │ -(arnie@host)1> init:get_argument(sname).
    │ │ │ -{ok,[["arnie"]]}
    │ │ │ -(arnie@host)2> init:get_plain_arguments().
    │ │ │ -["+bertie"]

    Here +W w and +R 9 are emulator flags. -s my_init is an init flag, │ │ │ +(arnie@host)1> init:get_argument(sname). │ │ │ +{ok,[["arnie"]]} │ │ │ +(arnie@host)2> init:get_plain_arguments(). │ │ │ +["+bertie"]

    Here +W w and +R 9 are emulator flags. -s my_init is an init flag, │ │ │ interpreted by init. -sname arnie is a user flag, stored by init. It is │ │ │ read by Kernel and causes the Erlang runtime system to become distributed. │ │ │ Finally, everything after -extra (that is, +bertie) is considered as plain │ │ │ arguments.

    % erl -myflag 1
    │ │ │ -1> init:get_argument(myflag).
    │ │ │ -{ok,[["1"]]}
    │ │ │ -2> init:get_plain_arguments().
    │ │ │ -[]

    Here the user flag -myflag 1 is passed to and stored by the init process. It │ │ │ +1> init:get_argument(myflag). │ │ │ +{ok,[["1"]]} │ │ │ +2> init:get_plain_arguments(). │ │ │ +[]

    Here the user flag -myflag 1 is passed to and stored by the init process. It │ │ │ is a user-defined flag, presumably used by some user-defined application.

    │ │ │ │ │ │ │ │ │ │ │ │ Flags │ │ │

    │ │ │

    In the following list, init flags are marked "(init flag)". Unless otherwise │ │ │ @@ -700,15 +700,15 @@ │ │ │ processes) into a smaller set of schedulers when schedulers frequently run │ │ │ out of work. When disabled, the frequency with which schedulers run out of │ │ │ work is not taken into account by the load balancing logic.

    +scl false is similar to +sub true, but +sub true │ │ │ also balances scheduler utilization between schedulers.

  • +sct CpuTopology - Sets a user-defined CPU topology. │ │ │ The user-defined CPU topology overrides │ │ │ any automatically detected CPU topology. The CPU topology is used when │ │ │ binding schedulers to logical processors. This option must be before │ │ │ -+sbt on the command-line.

    <Id> = integer(); when 0 =< <Id> =< 65535
    │ │ │ ++sbt on the command-line.

    <Id> = integer(); when 0 =< <Id> =< 65535
    │ │ │  <IdRange> = <Id>-<Id>
    │ │ │  <IdOrIdRange> = <Id> | <IdRange>
    │ │ │  <IdList> = <IdOrIdRange>,<IdOrIdRange> | <IdOrIdRange>
    │ │ │  <LogicalIds> = L<IdList>
    │ │ │  <ThreadIds> = T<IdList> | t<IdList>
    │ │ │  <CoreIds> = C<IdList> | c<IdList>
    │ │ │  <ProcessorIds> = P<IdList> | p<IdList>
    │ │ │ @@ -733,30 +733,30 @@
    │ │ │  node.
  • <LogicalIds><ThreadIds><CoreIds><NodeIds><ProcessorIds>, that is, thread │ │ │ is part of a core that is part of a NUMA node, which is part of a │ │ │ processor.
  • A CPU topology can consist of both processor external, and processor │ │ │ internal NUMA nodes as long as each logical processor belongs to only one │ │ │ NUMA node. If <ProcessorIds> is omitted, its default position is before │ │ │ <NodeIds>. That is, the default is processor external NUMA nodes.

    If a list of identifiers is used in an <IdDefs>:

    • <LogicalIds> must be a list of identifiers.
    • At least one other identifier type besides <LogicalIds> must also have a │ │ │ list of identifiers.
    • All lists of identifiers must produce the same number of identifiers.

    A simple example. A single quad core processor can be described as follows:

    % erl +sct L0-3c0-3
    │ │ │ -1> erlang:system_info(cpu_topology).
    │ │ │ -[{processor,[{core,{logical,0}},
    │ │ │ -             {core,{logical,1}},
    │ │ │ -             {core,{logical,2}},
    │ │ │ -             {core,{logical,3}}]}]

    A more complicated example with two quad core processors, each processor in │ │ │ +1> erlang:system_info(cpu_topology). │ │ │ +[{processor,[{core,{logical,0}}, │ │ │ + {core,{logical,1}}, │ │ │ + {core,{logical,2}}, │ │ │ + {core,{logical,3}}]}]

    A more complicated example with two quad core processors, each processor in │ │ │ its own NUMA node. The ordering of logical processors is a bit weird. This │ │ │ to give a better example of identifier lists:

    % erl +sct L0-1,3-2c0-3p0N0:L7,4,6-5c0-3p1N1
    │ │ │ -1> erlang:system_info(cpu_topology).
    │ │ │ -[{node,[{processor,[{core,{logical,0}},
    │ │ │ -                    {core,{logical,1}},
    │ │ │ -                    {core,{logical,3}},
    │ │ │ -                    {core,{logical,2}}]}]},
    │ │ │ - {node,[{processor,[{core,{logical,7}},
    │ │ │ -                    {core,{logical,4}},
    │ │ │ -                    {core,{logical,6}},
    │ │ │ -                    {core,{logical,5}}]}]}]

    As long as real identifiers are correct, it is OK to pass a CPU topology │ │ │ +1> erlang:system_info(cpu_topology). │ │ │ +[{node,[{processor,[{core,{logical,0}}, │ │ │ + {core,{logical,1}}, │ │ │ + {core,{logical,3}}, │ │ │ + {core,{logical,2}}]}]}, │ │ │ + {node,[{processor,[{core,{logical,7}}, │ │ │ + {core,{logical,4}}, │ │ │ + {core,{logical,6}}, │ │ │ + {core,{logical,5}}]}]}]

    As long as real identifiers are correct, it is OK to pass a CPU topology │ │ │ that is not a correct description of the CPU topology. When used with care │ │ │ this can be very useful. This to trick the emulator to bind its schedulers │ │ │ as you want. For example, if you want to run multiple Erlang runtime systems │ │ │ on the same machine, you want to reduce the number of schedulers used and │ │ │ manipulate the CPU topology so that they bind to different logical CPUs. An │ │ │ example, with two Erlang runtime systems on a quad core machine:

    % erl +sct L0-3c0-3 +sbt db +S3:2 -detached -noinput -noshell -sname one
    │ │ │  % erl +sct L3-0c0-3 +sbt db +S3:2 -detached -noinput -noshell -sname two

    In this example, each runtime system have two schedulers each online, and │ │ │ @@ -923,18 +923,18 @@ │ │ │ │ │ │

    The standard Erlang/OTP system can be reconfigured to change the default │ │ │ behavior on startup.

    • The .erlang startup file - When Erlang/OTP is started, the system │ │ │ searches for a file named .erlang in the │ │ │ user's home directory and then │ │ │ filename:basedir(user_config, "erlang").

      If an .erlang file is found, it is assumed to contain valid Erlang │ │ │ expressions. These expressions are evaluated as if they were input to the │ │ │ -shell.

      A typical .erlang file contains a set of search paths, for example:

      io:format("executing user profile in $HOME/.erlang\n",[]).
      │ │ │ -code:add_path("/home/calvin/test/ebin").
      │ │ │ -code:add_path("/home/hobbes/bigappl-1.2/ebin").
      │ │ │ -io:format(".erlang rc finished\n",[]).
    • user_default and shell_default - Functions in the shell that are not │ │ │ +shell.

      A typical .erlang file contains a set of search paths, for example:

      io:format("executing user profile in $HOME/.erlang\n",[]).
      │ │ │ +code:add_path("/home/calvin/test/ebin").
      │ │ │ +code:add_path("/home/hobbes/bigappl-1.2/ebin").
      │ │ │ +io:format(".erlang rc finished\n",[]).
    • user_default and shell_default - Functions in the shell that are not │ │ │ prefixed by a module name are assumed to be functional objects (funs), │ │ │ built-in functions (BIFs), or belong to the module user_default or │ │ │ shell_default.

      To include private shell commands, define them in a module user_default and │ │ │ add the following argument as the first line in the .erlang file:

      code:load_abs("..../user_default").
    • erl - If the contents of .erlang are changed and a private version of │ │ │ user_default is defined, the Erlang/OTP environment can be customized. More │ │ │ powerful changes can be made by supplying command-line arguments in the │ │ │ startup script erl. For more information, see init.

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/erl_dist_protocol.html │ │ │ @@ -252,32 +252,32 @@ │ │ │ --- │ │ │ sequenceDiagram │ │ │ participant client as Client (or Node) │ │ │ participant EPMD │ │ │ │ │ │ client ->> EPMD: NAMES_REQ │ │ │ EPMD -->> client: NAMES_RESP

    1
    110

    Table: NAMES_REQ (110)

    The response for a NAMES_REQ is as follows:

    4
    EPMDPortNoNodeInfo*

    Table: NAMES_RESP

    NodeInfo is a string written for each active node. When all NodeInfo has │ │ │ -been written the connection is closed by the EPMD.

    NodeInfo is, as expressed in Erlang:

    io:format("name ~ts at port ~p~n", [NodeName, Port]).

    │ │ │ +been written the connection is closed by the EPMD.

    NodeInfo is, as expressed in Erlang:

    io:format("name ~ts at port ~p~n", [NodeName, Port]).

    │ │ │ │ │ │ │ │ │ │ │ │ Dump All Data from EPMD │ │ │

    │ │ │

    This request is not really used, it is to be regarded as a debug feature.

    ---
    │ │ │  title: Dump All Data from EPMD
    │ │ │  ---
    │ │ │  sequenceDiagram
    │ │ │      participant client as Client (or Node)
    │ │ │      participant EPMD
    │ │ │      
    │ │ │      client ->> EPMD: DUMP_REQ
    │ │ │      EPMD -->> client: DUMP_RESP
    1
    100

    Table: DUMP_REQ

    The response for a DUMP_REQ is as follows:

    4
    EPMDPortNoNodeInfo*

    Table: DUMP_RESP

    NodeInfo is a string written for each node kept in the EPMD. When all │ │ │ -NodeInfo has been written the connection is closed by the EPMD.

    NodeInfo is, as expressed in Erlang:

    io:format("active name     ~ts at port ~p, fd = ~p~n",
    │ │ │ -          [NodeName, Port, Fd]).

    or

    io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
    │ │ │ -          [NodeName, Port, Fd]).

    │ │ │ +NodeInfo has been written the connection is closed by the EPMD.

    NodeInfo is, as expressed in Erlang:

    io:format("active name     ~ts at port ~p, fd = ~p~n",
    │ │ │ +          [NodeName, Port, Fd]).

    or

    io:format("old/unused name ~ts at port ~p, fd = ~p ~n",
    │ │ │ +          [NodeName, Port, Fd]).

    │ │ │ │ │ │ │ │ │ │ │ │ Kill EPMD │ │ │

    │ │ │

    This request kills the running EPMD. It is almost never used.

    ---
    │ │ │  title: Kill EPMD
    │ │ │ @@ -407,54 +407,54 @@
    │ │ │  received from A is correct and generates a digest from the challenge
    │ │ │  received from A. The digest is then sent to A. The message is as follows:

    116
    'a'Digest

    Table: The challenge_ack message

    Digest is the digest calculated by B for A's challenge.

  • 7) check - A checks the digest from B and the connection is up.

  • │ │ │ │ │ │ │ │ │ │ │ │ Semigraphic View │ │ │

    │ │ │ -
    A (initiator)                                      B (acceptor)
    │ │ │ +
    A (initiator)                                      B (acceptor)
    │ │ │  
    │ │ │  TCP connect ------------------------------------>
    │ │ │                                                     TCP accept
    │ │ │  
    │ │ │  send_name -------------------------------------->
    │ │ │                                                     recv_name
    │ │ │  
    │ │ │    <---------------------------------------------- send_status
    │ │ │  recv_status
    │ │ │ -(if status was 'alive'
    │ │ │ +(if status was 'alive'
    │ │ │   send_status - - - - - - - - - - - - - - - - - ->
    │ │ │ -                                                   recv_status)
    │ │ │ +                                                   recv_status)
    │ │ │  
    │ │ │ -                          (ChB)                      ChB = gen_challenge()
    │ │ │ +                          (ChB)                      ChB = gen_challenge()
    │ │ │    <---------------------------------------------- send_challenge
    │ │ │  recv_challenge
    │ │ │  
    │ │ │ -(if old send_name
    │ │ │ +(if old send_name
    │ │ │   send_complement - - - - - - - - - - - - - - - ->
    │ │ │ -                                                   recv_complement)
    │ │ │ +                                                   recv_complement)
    │ │ │  
    │ │ │ -ChA = gen_challenge(),
    │ │ │ -OCA = out_cookie(B),
    │ │ │ -DiA = gen_digest(ChB, OCA)
    │ │ │ -                          (ChA, DiA)
    │ │ │ +ChA = gen_challenge(),
    │ │ │ +OCA = out_cookie(B),
    │ │ │ +DiA = gen_digest(ChB, OCA)
    │ │ │ +                          (ChA, DiA)
    │ │ │  send_challenge_reply --------------------------->
    │ │ │                                                     recv_challenge_reply
    │ │ │ -                                                   ICB = in_cookie(A),
    │ │ │ +                                                   ICB = in_cookie(A),
    │ │ │                                                     check:
    │ │ │ -                                                   DiA == gen_digest (ChB, ICB)?
    │ │ │ +                                                   DiA == gen_digest (ChB, ICB)?
    │ │ │                                                     - if OK:
    │ │ │ -                                                    OCB = out_cookie(A),
    │ │ │ -                                                    DiB = gen_digest (ChA, OCB)
    │ │ │ -                          (DiB)
    │ │ │ +                                                    OCB = out_cookie(A),
    │ │ │ +                                                    DiB = gen_digest (ChA, OCB)
    │ │ │ +                          (DiB)
    │ │ │    <----------------------------------------------- send_challenge_ack
    │ │ │  recv_challenge_ack                                  DONE
    │ │ │ -ICA = in_cookie(B),                                - else:
    │ │ │ +ICA = in_cookie(B),                                - else:
    │ │ │  check:                                              CLOSE
    │ │ │ -DiB == gen_digest(ChA, ICA)?
    │ │ │ +DiB == gen_digest(ChA, ICA)?
    │ │ │  - if OK:
    │ │ │   DONE
    │ │ │  - else:
    │ │ │   CLOSE

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/erl_ext_dist.html │ │ │ @@ -436,15 +436,15 @@ │ │ │ │ │ │ SMALL_BIG_EXT │ │ │

    │ │ │
    111n
    110nSignd(0) ... d(n-1)

    Bignums are stored in unary form with a Sign byte, that is, 0 if the bignum is │ │ │ positive and 1 if it is negative. The digits are stored with the least │ │ │ significant byte stored first. To calculate the integer, the following formula │ │ │ can be used:

    B = 256
    │ │ │ -(d0*B^0 + d1*B^1 + d2*B^2 + ... d(N-1)*B^(n-1))

    │ │ │ +(d0*B^0 + d1*B^1 + d2*B^2 + ... d(N-1)*B^(n-1))

    │ │ │ │ │ │ │ │ │ │ │ │ LARGE_BIG_EXT │ │ │

    │ │ │
    141n
    111nSignd(0) ... d(n-1)

    Same as SMALL_BIG_EXT except that the length │ │ │ field is an unsigned 4 byte integer.

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/erl_nif.html │ │ │ @@ -161,27 +161,27 @@ │ │ │ } │ │ │ │ │ │ static ErlNifFunc nif_funcs[] = │ │ │ { │ │ │ {"hello", 0, hello} │ │ │ }; │ │ │ │ │ │ -ERL_NIF_INIT(niftest,nif_funcs,NULL,NULL,NULL,NULL)

    The Erlang module can look as follows:

    -module(niftest).
    │ │ │ +ERL_NIF_INIT(niftest,nif_funcs,NULL,NULL,NULL,NULL)

    The Erlang module can look as follows:

    -module(niftest).
    │ │ │  
    │ │ │ --export([init/0, hello/0]).
    │ │ │ +-export([init/0, hello/0]).
    │ │ │  
    │ │ │ --nifs([hello/0]).
    │ │ │ +-nifs([hello/0]).
    │ │ │  
    │ │ │ --on_load(init/0).
    │ │ │ +-on_load(init/0).
    │ │ │  
    │ │ │ -init() ->
    │ │ │ -      erlang:load_nif("./niftest", 0).
    │ │ │ +init() ->
    │ │ │ +      erlang:load_nif("./niftest", 0).
    │ │ │  
    │ │ │ -hello() ->
    │ │ │ -      erlang:nif_error("NIF library not loaded").

    Compile and test can look as follows (on Linux):

    $> gcc -fPIC -shared -o niftest.so niftest.c -I $ERL_ROOT/usr/include/
    │ │ │ +hello() ->
    │ │ │ +      erlang:nif_error("NIF library not loaded").

    Compile and test can look as follows (on Linux):

    $> gcc -fPIC -shared -o niftest.so niftest.c -I $ERL_ROOT/usr/include/
    │ │ │  $> erl
    │ │ │  
    │ │ │  1> c(niftest).
    │ │ │  {ok,niftest}
    │ │ │  2> niftest:hello().
    │ │ │  "Hello world!"

    In the example above the on_load │ │ │ directive is used get function init called automatically when the module is │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.3/doc/html/erl_prim_loader.html │ │ │ @@ -398,15 +398,15 @@ │ │ │ when Filename :: string(), FileInfo :: file:file_info().

    │ │ │ │ │ │ │ │ │ │ │ │

    Retrieves information about a file.

    Returns {ok, FileInfo} if successful, otherwise error. FileInfo is a │ │ │ record file_info, defined in the Kernel include file │ │ │ file.hrl. Include the following directive in the module from which the │ │ │ -function is called:

    -include_lib("kernel/include/file.hrl").

    For more information about the record see file:read_file_info/2.

    Filename can also be a file in an archive, for example, │ │ │ +function is called:

    -include_lib("kernel/include/file.hrl").

    For more information about the record see file:read_file_info/2.

    Filename can also be a file in an archive, for example, │ │ │ $OTPROOT/lib/mnesia-4.4.7.ez/mnesia-4.4.7/ebin/mnesia. For information │ │ │ about archive files, see code.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Combines two previously computed adler32 checksums.

    This computation requires the size of the data object for the second checksum │ │ │ -to be known.

    The following code:

    Y = erlang:adler32(Data1),
    │ │ │ -Z = erlang:adler32(Y,Data2).

    assigns the same value to Z as this:

    X = erlang:adler32(Data1),
    │ │ │ -Y = erlang:adler32(Data2),
    │ │ │ -Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).
    │ │ │ +to be known.

    The following code:

    Y = erlang:adler32(Data1),
    │ │ │ +Z = erlang:adler32(Y,Data2).

    assigns the same value to Z as this:

    X = erlang:adler32(Data1),
    │ │ │ +Y = erlang:adler32(Data2),
    │ │ │ +Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -6934,16 +6934,16 @@ │ │ │ │ │ │ │ │ │

    Continues computing the crc32 checksum by combining the previous checksum, │ │ │ -OldCrc, with the checksum of Data.

    The following code:

    X = erlang:crc32(Data1),
    │ │ │ -Y = erlang:crc32(X,Data2).

    assigns the same value to Y as this:

    Y = erlang:crc32([Data1,Data2]).
    │ │ │ +OldCrc, with the checksum of Data.

    The following code:

    X = erlang:crc32(Data1),
    │ │ │ +Y = erlang:crc32(X,Data2).

    assigns the same value to Y as this:

    Y = erlang:crc32([Data1,Data2]).
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Combines two previously computed crc32 checksums.

    This computation requires the size of the data object for the second checksum │ │ │ -to be known.

    The following code:

    Y = erlang:crc32(Data1),
    │ │ │ -Z = erlang:crc32(Y,Data2).

    assigns the same value to Z as this:

    X = erlang:crc32(Data1),
    │ │ │ -Y = erlang:crc32(Data2),
    │ │ │ -Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).
    │ │ │ +to be known.

    The following code:

    Y = erlang:crc32(Data1),
    │ │ │ +Z = erlang:crc32(Y,Data2).

    assigns the same value to Z as this:

    X = erlang:crc32(Data1),
    │ │ │ +Y = erlang:crc32(Data2),
    │ │ │ +Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8129,19 +8129,19 @@ │ │ │ the Info map in the returned result will contain the key node_type │ │ │ associated with the value NodeTypeInfo. Currently the following node types │ │ │ exist:

    Example:

    (a@localhost)1> nodes([this, connected], #{connection_id=>true, node_type=>true}).
    │ │ │ -[{c@localhost,#{connection_id => 13892108,node_type => hidden}},
    │ │ │ - {b@localhost,#{connection_id => 3067553,node_type => visible}},
    │ │ │ - {a@localhost,#{connection_id => undefined,node_type => this}}]
    │ │ │ -(a@localhost)2>
    │ │ │ +process.

    Example:

    (a@localhost)1> nodes([this, connected], #{connection_id=>true, node_type=>true}).
    │ │ │ +[{c@localhost,#{connection_id => 13892108,node_type => hidden}},
    │ │ │ + {b@localhost,#{connection_id => 3067553,node_type => visible}},
    │ │ │ + {a@localhost,#{connection_id => undefined,node_type => this}}]
    │ │ │ +(a@localhost)2>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8237,17 +8237,17 @@ │ │ │ │ │ │
    -spec abs(Float) -> float() when Float :: float();
    │ │ │           (Int) -> non_neg_integer() when Int :: integer().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns an integer or float that is the arithmetical absolute value of Float │ │ │ -or Int.

    For example:

    > abs(-3.33).
    │ │ │ +or Int.

    For example:

    > abs(-3.33).
    │ │ │  3.33
    │ │ │ -> abs(-3).
    │ │ │ +> abs(-3).
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a new tuple that has one element more than Tuple1, and contains the │ │ │ elements in Tuple1 followed by Term as the last element.

    Semantically equivalent to │ │ │ list_to_tuple(tuple_to_list(Tuple1) ++ [Term]), but much │ │ │ -faster.

    For example:

    > erlang:append_element({one, two}, three).
    │ │ │ -{one,two,three}
    │ │ │ +faster.

    For example:

    > erlang:append_element({one, two}, three).
    │ │ │ +{one,two,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a binary corresponding to the text representation of Atom.

    If Encoding is latin1, one byte exists for each character in the text │ │ │ representation. If Encoding is utf8 or unicode, the characters are encoded │ │ │ using UTF-8 where characters may require multiple bytes.

    Change

    As from Erlang/OTP 20, atoms can contain any Unicode character and │ │ │ atom_to_binary(Atom, latin1) may fail if the text │ │ │ -representation for Atom contains a Unicode character > 255.

    Example:

    > atom_to_binary('Erlang', latin1).
    │ │ │ -<<"Erlang">>
    │ │ │ +representation for Atom contains a Unicode character > 255.

    Example:

    > atom_to_binary('Erlang', latin1).
    │ │ │ +<<"Erlang">>
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8363,17 +8363,17 @@ │ │ │
    │ │ │ │ │ │
    -spec atom_to_list(Atom) -> string() when Atom :: atom().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a list of unicode code points corresponding to the text representation │ │ │ -of Atom.

    For example:

    > atom_to_list('Erlang').
    │ │ │ -"Erlang"
    > atom_to_list('你好').
    │ │ │ -[20320,22909]

    See unicode for how to convert the resulting list to different formats.

    │ │ │ +of Atom.

    For example:

    > atom_to_list('Erlang').
    │ │ │ +"Erlang"
    > atom_to_list('你好').
    │ │ │ +[20320,22909]

    See unicode for how to convert the resulting list to different formats.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8400,19 +8400,19 @@ │ │ │
    -spec binary_part(Subject, PosLen) -> binary()
    │ │ │                       when
    │ │ │                           Subject :: binary(),
    │ │ │                           PosLen :: {Start :: non_neg_integer(), Length :: integer()}.
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Extracts the part of the binary described by PosLen.

    Negative length can be used to extract bytes at the end of a binary.

    For example:

    1> Bin = <<1,2,3,4,5,6,7,8,9,10>>.
    │ │ │ -2> binary_part(Bin,{byte_size(Bin), -5}).
    │ │ │ -<<6,7,8,9,10>>

    Failure: badarg if PosLen in any way references outside the binary.

    Start is zero-based, that is:

    1> Bin = <<1,2,3>>
    │ │ │ -2> binary_part(Bin,{0,2}).
    │ │ │ -<<1,2>>

    For details about the PosLen semantics, see binary.

    │ │ │ +

    Extracts the part of the binary described by PosLen.

    Negative length can be used to extract bytes at the end of a binary.

    For example:

    1> Bin = <<1,2,3,4,5,6,7,8,9,10>>.
    │ │ │ +2> binary_part(Bin,{byte_size(Bin), -5}).
    │ │ │ +<<6,7,8,9,10>>

    Failure: badarg if PosLen in any way references outside the binary.

    Start is zero-based, that is:

    1> Bin = <<1,2,3>>
    │ │ │ +2> binary_part(Bin,{0,2}).
    │ │ │ +<<1,2>>

    For details about the PosLen semantics, see binary.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │

    Note

    The number of characters that are permitted in an atom name is limited. The │ │ │ default limits can be found in the │ │ │ Efficiency Guide (section System Limits).

    Note

    There is configurable limit on how many atoms that can exist and atoms are not │ │ │ garbage collected. Therefore, it is recommended to consider whether │ │ │ binary_to_existing_atom/2 is a better option │ │ │ than binary_to_atom/2. The default limits can be found │ │ │ -in Efficiency Guide (section System Limits).

    Examples:

    > binary_to_atom(<<"Erlang">>, latin1).
    │ │ │ -'Erlang'
    > binary_to_atom(<<1024/utf8>>, utf8).
    │ │ │ +in Efficiency Guide (section System Limits).

    Examples:

    > binary_to_atom(<<"Erlang">>, latin1).
    │ │ │ +'Erlang'
    > binary_to_atom(<<1024/utf8>>, utf8).
    │ │ │  'Ѐ'
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8613,15 +8613,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec binary_to_float(Binary) -> float() when Binary :: binary().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the float whose text representation is Binary.

    For example:

    > binary_to_float(<<"2.2017764e+0">>).
    │ │ │ +

    Returns the float whose text representation is Binary.

    For example:

    > binary_to_float(<<"2.2017764e+0">>).
    │ │ │  2.2017764

    The float string format is the same as the format for │ │ │ Erlang float literals except for that underscores │ │ │ are not permitted.

    Failure: badarg if Binary contains a bad representation of a float.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8646,15 +8646,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec binary_to_integer(Binary) -> integer() when Binary :: binary().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer whose text representation is Binary.

    For example:

    > binary_to_integer(<<"123">>).
    │ │ │ +

    Returns an integer whose text representation is Binary.

    For example:

    > binary_to_integer(<<"123">>).
    │ │ │  123

    binary_to_integer/1 accepts the same string formats │ │ │ as list_to_integer/1.

    Failure: badarg if Binary contains a bad representation of an integer.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -8678,15 +8678,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec binary_to_integer(Binary, Base) -> integer() when Binary :: binary(), Base :: 2..36.
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer whose text representation in base Base is Binary.

    For example:

    > binary_to_integer(<<"3FF">>, 16).
    │ │ │ +

    Returns an integer whose text representation in base Base is Binary.

    For example:

    > binary_to_integer(<<"3FF">>, 16).
    │ │ │  1023

    binary_to_integer/2 accepts the same string formats │ │ │ as list_to_integer/2.

    Failure: badarg if Binary contains a bad representation of an integer.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -8771,17 +8771,17 @@ │ │ │ │ │ │
    -spec binary_to_term(Binary) -> term() when Binary :: ext_binary().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns an Erlang term that is the result of decoding binary object Binary, │ │ │ which must be encoded according to the │ │ │ -Erlang external term format.

    > Bin = term_to_binary(hello).
    │ │ │ -<<131,100,0,5,104,101,108,108,111>>
    │ │ │ -> hello = binary_to_term(Bin).
    │ │ │ +Erlang external term format.

    > Bin = term_to_binary(hello).
    │ │ │ +<<131,100,0,5,104,101,108,108,111>>
    │ │ │ +> hello = binary_to_term(Bin).
    │ │ │  hello

    Warning

    When decoding binaries from untrusted sources, the untrusted source may submit │ │ │ data in a way to create resources, such as atoms and remote references, that │ │ │ cannot be garbage collected and lead to Denial of Service attack. In such │ │ │ cases, consider using binary_to_term/2 with the safe │ │ │ option.

    See also term_to_binary/1 and binary_to_term/2.

    │ │ │
    │ │ │ │ │ │ @@ -8820,30 +8820,30 @@ │ │ │

    Equivalent to binary_to_term(Binary), but can be configured to │ │ │ fit special purposes.

    The allowed options are:

    • safe - Use this option when receiving binaries from an untrusted source.

      When enabled, it prevents decoding data that can be used to attack the Erlang │ │ │ runtime. In the event of receiving unsafe data, decoding fails with a badarg │ │ │ error.

      This prevents creation of new atoms directly, creation of new atoms indirectly │ │ │ (as they are embedded in certain structures, such as process identifiers, │ │ │ refs, and funs), and creation of new external function references. None of │ │ │ those resources are garbage collected, so unchecked creation of them can │ │ │ -exhaust available memory.

      > binary_to_term(<<131,100,0,5,"hello">>, [safe]).
      │ │ │ +exhaust available memory.

      > binary_to_term(<<131,100,0,5,"hello">>, [safe]).
      │ │ │  ** exception error: bad argument
      │ │ │  > hello.
      │ │ │  hello
      │ │ │ -> binary_to_term(<<131,100,0,5,"hello">>, [safe]).
      │ │ │ +> binary_to_term(<<131,100,0,5,"hello">>, [safe]).
      │ │ │  hello

      Warning

      The safe option ensures the data is safely processed by the Erlang runtime │ │ │ but it does not guarantee the data is safe to your application. You must │ │ │ always validate data from untrusted sources. If the binary is stored or │ │ │ transits through untrusted sources, you should also consider │ │ │ cryptographically signing it.

    • used - Changes the return value to {Term, Used} where Used is the │ │ │ -number of bytes actually read from Binary.

      > Input = <<131,100,0,5,"hello","world">>.
      │ │ │ -<<131,100,0,5,104,101,108,108,111,119,111,114,108,100>>
      │ │ │ -> {Term, Used} = binary_to_term(Input, [used]).
      │ │ │ -{hello, 9}
      │ │ │ -> split_binary(Input, Used).
      │ │ │ -{<<131,100,0,5,104,101,108,108,111>>, <<"world">>}

    Failure: badarg if safe is specified and unsafe data is decoded.

    See also term_to_binary/1, binary_to_term/1, and list_to_existing_atom/1.

    │ │ │ +number of bytes actually read from Binary.

    > Input = <<131,100,0,5,"hello","world">>.
    │ │ │ +<<131,100,0,5,104,101,108,108,111,119,111,114,108,100>>
    │ │ │ +> {Term, Used} = binary_to_term(Input, [used]).
    │ │ │ +{hello, 9}
    │ │ │ +> split_binary(Input, Used).
    │ │ │ +{<<131,100,0,5,104,101,108,108,111>>, <<"world">>}

    Failure: badarg if safe is specified and unsafe data is decoded.

    See also term_to_binary/1, binary_to_term/1, and list_to_existing_atom/1.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8865,17 +8865,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

    Returns an integer that is the size in bits of Bitstring.

    For example:

    > bit_size(<<433:16,3:3>>).
    │ │ │ +

    Returns an integer that is the size in bits of Bitstring.

    For example:

    > bit_size(<<433:16,3:3>>).
    │ │ │  19
    │ │ │ -> bit_size(<<1,2,3>>).
    │ │ │ +> bit_size(<<1,2,3>>).
    │ │ │  24
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8897,17 +8897,17 @@ │ │ │ │ │ │ │ │ │

    Returns a list of integers corresponding to the bytes of Bitstring.

    If the number of bits in the binary is not divisible by 8, the last element of │ │ │ -the list is a bitstring containing the remaining 1-7 bits.

    For example:

    > bitstring_to_list(<<433:16>>).
    │ │ │ -[1,177]
    > bitstring_to_list(<<433:16,3:3>>).
    │ │ │ -[1,177,<<3:3>>]
    │ │ │ +the list is a bitstring containing the remaining 1-7 bits.

    For example:

    > bitstring_to_list(<<433:16>>).
    │ │ │ +[1,177]
    > bitstring_to_list(<<433:16,3:3>>).
    │ │ │ +[1,177,<<3:3>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns an integer that is the number of bytes needed to contain Bitstring. │ │ │ That is, if the number of bits in Bitstring is not divisible by 8, the │ │ │ -resulting number of bytes is rounded up.

    For example:

    > byte_size(<<433:16,3:3>>).
    │ │ │ +resulting number of bytes is rounded up.

    For example:

    > byte_size(<<433:16,3:3>>).
    │ │ │  3
    │ │ │ -> byte_size(<<1,2,3>>).
    │ │ │ +> byte_size(<<1,2,3>>).
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8966,15 +8966,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec ceil(Number) -> integer() when Number :: number().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the smallest integer not less than Number.

    For example:

    > ceil(5.5).
    │ │ │ +

    Returns the smallest integer not less than Number.

    For example:

    > ceil(5.5).
    │ │ │  6
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9074,18 +9074,18 @@ │ │ │ RFC2732 .

    Options:

    • {packet_size, integer() >= 0} - Sets the maximum allowed size of the │ │ │ packet body. If the packet header indicates that the length of the packet is │ │ │ longer than the maximum allowed length, the packet is considered invalid. │ │ │ Defaults to 0, which means no size limit.

    • {line_length, integer() >= 0} - For packet type line, lines longer │ │ │ than the indicated length are truncated.

      Option line_length also applies to http* packet types as an alias for │ │ │ option packet_size if packet_size itself is not set. This use is only │ │ │ intended for backward compatibility.

    • {line_delimiter, 0 =< byte() =< 255} - For packet type line, sets the │ │ │ -delimiting byte. Default is the latin-1 character $\n.

    Examples:

    > erlang:decode_packet(1,<<3,"abcd">>,[]).
    │ │ │ -{ok,<<"abc">>,<<"d">>}
    │ │ │ -> erlang:decode_packet(1,<<5,"abcd">>,[]).
    │ │ │ -{more,6}
    │ │ │ +delimiting byte. Default is the latin-1 character $\n.

    Examples:

    > erlang:decode_packet(1,<<3,"abcd">>,[]).
    │ │ │ +{ok,<<"abc">>,<<"d">>}
    │ │ │ +> erlang:decode_packet(1,<<5,"abcd">>,[]).
    │ │ │ +{more,6}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9106,16 +9106,16 @@ │ │ │ │ │ │ │ │ │ -

    Returns a new tuple with element at Index removed from tuple Tuple1.

    For example:

    > erlang:delete_element(2, {one, two, three}).
    │ │ │ -{one,three}
    │ │ │ +

    Returns a new tuple with element at Index removed from tuple Tuple1.

    For example:

    > erlang:delete_element(2, {one, two, three}).
    │ │ │ +{one,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9165,15 +9165,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec element(N, Tuple) -> term() when N :: pos_integer(), Tuple :: tuple().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the Nth element (numbering from 1) of Tuple.

    For example:

    > element(2, {a, b, c}).
    │ │ │ +

    Returns the Nth element (numbering from 1) of Tuple.

    For example:

    > element(2, {a, b, c}).
    │ │ │  b
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9195,18 +9195,18 @@ │ │ │ │ │ │ │ │ │

    Calculates, without doing the encoding, the maximum byte size for a term encoded │ │ │ -in the Erlang external term format.

    The following condition applies always:

    > Size1 = byte_size(term_to_binary(Term)),
    │ │ │ -> Size2 = erlang:external_size(Term),
    │ │ │ +in the Erlang external term format.

    The following condition applies always:

    > Size1 = byte_size(term_to_binary(Term)),
    │ │ │ +> Size2 = erlang:external_size(Term),
    │ │ │  > true = Size1 =< Size2.
    │ │ │ -true

    This is equivalent to a call to:

    erlang:external_size(Term, [])
    │ │ │ +
    true

    This is equivalent to a call to:

    erlang:external_size(Term, [])
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Calculates, without doing the encoding, the maximum byte size for a term encoded │ │ │ -in the Erlang external term format.

    The following condition applies always:

    > Size1 = byte_size(term_to_binary(Term, Options)),
    │ │ │ -> Size2 = erlang:external_size(Term, Options),
    │ │ │ +in the Erlang external term format.

    The following condition applies always:

    > Size1 = byte_size(term_to_binary(Term, Options)),
    │ │ │ +> Size2 = erlang:external_size(Term, Options),
    │ │ │  > true = Size1 =< Size2.
    │ │ │  true

    Option {minor_version, Version} specifies how floats are encoded. For a │ │ │ detailed description, see term_to_binary/2.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9269,15 +9269,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec float(Number) -> float() when Number :: number().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a float by converting Number to a float.

    For example:

    > float(55).
    │ │ │ +

    Returns a float by converting Number to a float.

    For example:

    > float(55).
    │ │ │  55.0

    Note

    If used on the top level in a guard, it tests whether the argument is a │ │ │ floating point number; for clarity, use is_float/1 instead.

    When float/1 is used in an expression in a guard, such as │ │ │ 'float(A) == 4.0', it converts a number as described earlier.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9340,26 +9340,26 @@ │ │ │ {decimals, Decimals :: 0..253} | │ │ │ {scientific, Decimals :: 0..249} | │ │ │ compact | short.
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a binary corresponding to the text representation of Float using fixed │ │ │ -decimal point formatting.

    Options behaves in the same way as float_to_list/2.

    For example:

    > float_to_binary(7.12, [{decimals, 4}]).
    │ │ │ -<<"7.1200">>
    │ │ │ -> float_to_binary(7.12, [{decimals, 4}, compact]).
    │ │ │ -<<"7.12">>
    │ │ │ -> float_to_binary(7.12, [{scientific, 3}]).
    │ │ │ -<<"7.120e+00">>
    │ │ │ -> float_to_binary(7.12, [short]).
    │ │ │ -<<"7.12">>
    │ │ │ -> float_to_binary(0.1+0.2, [short]).
    │ │ │ -<<"0.30000000000000004">>
    │ │ │ -> float_to_binary(0.1+0.2)
    │ │ │ -<<"3.00000000000000044409e-01">>
    │ │ │ +decimal point formatting.

    Options behaves in the same way as float_to_list/2.

    For example:

    > float_to_binary(7.12, [{decimals, 4}]).
    │ │ │ +<<"7.1200">>
    │ │ │ +> float_to_binary(7.12, [{decimals, 4}, compact]).
    │ │ │ +<<"7.12">>
    │ │ │ +> float_to_binary(7.12, [{scientific, 3}]).
    │ │ │ +<<"7.120e+00">>
    │ │ │ +> float_to_binary(7.12, [short]).
    │ │ │ +<<"7.12">>
    │ │ │ +> float_to_binary(0.1+0.2, [short]).
    │ │ │ +<<"0.30000000000000004">>
    │ │ │ +> float_to_binary(0.1+0.2)
    │ │ │ +<<"3.00000000000000044409e-01">>
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9427,25 +9427,25 @@ │ │ │ are truncated. This option is only meaningful together with option decimals.
  • If option scientific is specified, the float is formatted using scientific │ │ │ notation with Decimals digits of precision.
  • If option short is specified, the float is formatted with the smallest │ │ │ number of digits that still guarantees that │ │ │ F =:= list_to_float(float_to_list(F, [short])). When the float is inside the │ │ │ range (-2⁵³, 2⁵³), the notation that yields the smallest number of characters │ │ │ is used (scientific notation or normal decimal notation). Floats outside the │ │ │ range (-2⁵³, 2⁵³) are always formatted using scientific notation to avoid │ │ │ -confusing results when doing arithmetic operations.
  • If Options is [], the function behaves as float_to_list/1.
  • Examples:

    > float_to_list(7.12, [{decimals, 4}]).
    │ │ │ +confusing results when doing arithmetic operations.
  • If Options is [], the function behaves as float_to_list/1.
  • Examples:

    > float_to_list(7.12, [{decimals, 4}]).
    │ │ │  "7.1200"
    │ │ │ -> float_to_list(7.12, [{decimals, 4}, compact]).
    │ │ │ +> float_to_list(7.12, [{decimals, 4}, compact]).
    │ │ │  "7.12"
    │ │ │ -> float_to_list(7.12, [{scientific, 3}]).
    │ │ │ +> float_to_list(7.12, [{scientific, 3}]).
    │ │ │  "7.120e+00"
    │ │ │ -> float_to_list(7.12, [short]).
    │ │ │ +> float_to_list(7.12, [short]).
    │ │ │  "7.12"
    │ │ │ -> float_to_list(0.1+0.2, [short]).
    │ │ │ +> float_to_list(0.1+0.2, [short]).
    │ │ │  "0.30000000000000004"
    │ │ │ -> float_to_list(0.1+0.2)
    │ │ │ +> float_to_list(0.1+0.2)
    │ │ │  "3.00000000000000044409e-01"

    In the last example, float_to_list(0.1+0.2) evaluates to │ │ │ "3.00000000000000044409e-01". The reason for this is explained in │ │ │ Representation of Floating Point Numbers.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9472,15 +9472,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec floor(Number) -> integer() when Number :: number().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the largest integer not greater than Number.

    For example:

    > floor(-10.5).
    │ │ │ +

    Returns the largest integer not greater than Number.

    For example:

    > floor(-10.5).
    │ │ │  -11
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9600,25 +9600,25 @@ │ │ │ named module, index and uniq in the result of │ │ │ erlang:fun_info(Fun).

  • uncompiled code - All funs created from fun expressions in uncompiled code │ │ │ with the same arity are mapped to the same list by │ │ │ fun_to_list/1.

  • Note

    Generally, one can not use fun_to_list/1 to check if two │ │ │ funs are equal as fun_to_list/1 does not take the fun's │ │ │ environment into account. See erlang:fun_info/1 for how to │ │ │ get the environment of a fun.

    Change

    The output of fun_to_list/1 can differ between Erlang │ │ │ -implementations and may change in future versions.

    Examples:

    -module(test).
    │ │ │ --export([add/1, add2/0, fun_tuple/0]).
    │ │ │ -add(A) -> fun(B) -> A + B end.
    │ │ │ -add2() -> fun add/1.
    │ │ │ -fun_tuple() -> {fun() -> 1 end, fun() -> 1 end}.
    > {fun test:add/1, test:add2()}.
    │ │ │ -{fun test:add/1,#Fun<test.1.107738983>}

    Explanation: fun test:add/1 is upgradable but test:add2() is not upgradable.

    > {test:add(1), test:add(42)}.
    │ │ │ -{#Fun<test.0.107738983>,#Fun<test.0.107738983>}

    Explanation: test:add(1) and test:add(42) has the same string representation │ │ │ -as the environment is not taken into account.

    >test:fun_tuple().
    │ │ │ -{#Fun<test.2.107738983>,#Fun<test.3.107738983>}

    Explanation: The string representations differ because the funs come from │ │ │ -different fun expressions.

    > {fun() -> 1 end, fun() -> 1 end}. >
    │ │ │ -{#Fun<erl_eval.45.97283095>,#Fun<erl_eval.45.97283095>}

    Explanation: All funs created from fun expressions of this form in uncompiled │ │ │ +implementations and may change in future versions.

    Examples:

    -module(test).
    │ │ │ +-export([add/1, add2/0, fun_tuple/0]).
    │ │ │ +add(A) -> fun(B) -> A + B end.
    │ │ │ +add2() -> fun add/1.
    │ │ │ +fun_tuple() -> {fun() -> 1 end, fun() -> 1 end}.
    > {fun test:add/1, test:add2()}.
    │ │ │ +{fun test:add/1,#Fun<test.1.107738983>}

    Explanation: fun test:add/1 is upgradable but test:add2() is not upgradable.

    > {test:add(1), test:add(42)}.
    │ │ │ +{#Fun<test.0.107738983>,#Fun<test.0.107738983>}

    Explanation: test:add(1) and test:add(42) has the same string representation │ │ │ +as the environment is not taken into account.

    >test:fun_tuple().
    │ │ │ +{#Fun<test.2.107738983>,#Fun<test.3.107738983>}

    Explanation: The string representations differ because the funs come from │ │ │ +different fun expressions.

    > {fun() -> 1 end, fun() -> 1 end}. >
    │ │ │ +{#Fun<erl_eval.45.97283095>,#Fun<erl_eval.45.97283095>}

    Explanation: All funs created from fun expressions of this form in uncompiled │ │ │ code with the same arity are mapped to the same list by │ │ │ fun_to_list/1.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -9642,16 +9642,16 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec hd(List) -> Head when List :: nonempty_maybe_improper_list(), Head :: term().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the head of List, that is, the first element.

    It works with improper lists.

    Examples:

    > hd([1,2,3,4,5]).
    │ │ │ -1
    > hd([first, second, third, so_on | improper_end]).
    │ │ │ +

    Returns the head of List, that is, the first element.

    It works with improper lists.

    Examples:

    > hd([1,2,3,4,5]).
    │ │ │ +1
    > hd([first, second, third, so_on | improper_end]).
    │ │ │  first

    Failure: badarg if List is an empty list [].

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a new tuple with element Term inserted at position Index in tuple │ │ │ Tuple1. All elements from position Index and upwards are pushed one step │ │ │ -higher in the new tuple Tuple2.

    For example:

    > erlang:insert_element(2, {one, two, three}, new).
    │ │ │ -{one,new,two,three}
    │ │ │ +higher in the new tuple Tuple2.

    For example:

    > erlang:insert_element(2, {one, two, three}, new).
    │ │ │ +{one,new,two,three}
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9707,16 +9707,16 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

    Returns a binary corresponding to the text representation of Integer.

    For example:

    > integer_to_binary(77).
    │ │ │ -<<"77">>
    │ │ │ +

    Returns a binary corresponding to the text representation of Integer.

    For example:

    > integer_to_binary(77).
    │ │ │ +<<"77">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9739,16 +9739,16 @@ │ │ │
    │ │ │ │ │ │
    -spec integer_to_binary(Integer, Base) -> binary() when Integer :: integer(), Base :: 2..36.
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a binary corresponding to the text representation of Integer in base │ │ │ -Base.

    For example:

    > integer_to_binary(1023, 16).
    │ │ │ -<<"3FF">>
    │ │ │ +Base.

    For example:

    > integer_to_binary(1023, 16).
    │ │ │ +<<"3FF">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9768,15 +9768,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

    Returns a string corresponding to the text representation of Integer.

    For example:

    > integer_to_list(77).
    │ │ │ +

    Returns a string corresponding to the text representation of Integer.

    For example:

    > integer_to_list(77).
    │ │ │  "77"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9798,15 +9798,15 @@ │ │ │
    │ │ │ │ │ │
    -spec integer_to_list(Integer, Base) -> string() when Integer :: integer(), Base :: 2..36.
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a string corresponding to the text representation of Integer in base │ │ │ -Base.

    For example:

    > integer_to_list(1023, 16).
    │ │ │ +Base.

    For example:

    > integer_to_list(1023, 16).
    │ │ │  "3FF"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9828,15 +9828,15 @@ │ │ │ │ │ │ │ │ │

    Returns an integer, that is the size in bytes, of the binary that would be the │ │ │ -result of iolist_to_binary(Item).

    For example:

    > iolist_size([1,2|<<3,4>>]).
    │ │ │ +result of iolist_to_binary(Item).

    For example:

    > iolist_size([1,2|<<3,4>>]).
    │ │ │  4
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9858,22 +9858,22 @@ │ │ │
    │ │ │ │ │ │
    -spec iolist_to_binary(IoListOrBinary) -> binary() when IoListOrBinary :: iolist() | binary().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a binary that is made from the integers and binaries in │ │ │ -IoListOrBinary.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │ +IoListOrBinary.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9899,31 +9899,31 @@ │ │ │ │ │ │

    Returns an iovec that is made from the integers and binaries in │ │ │ IoListOrBinary. This function is useful when you want to flatten an iolist but │ │ │ you do not need a single binary. This can be useful for passing the data to nif │ │ │ functions such as enif_inspect_iovec or do │ │ │ more efficient message passing. The advantage of using this function over │ │ │ iolist_to_binary/1 is that it does not have to copy │ │ │ -off-heap binaries.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ +off-heap binaries.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │  %% If you pass small binaries and integers it works as iolist_to_binary
    │ │ │ -> erlang:iolist_to_iovec([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -[<<1,2,3,1,2,3,4,5,4,6>>]
    │ │ │ +> erlang:iolist_to_iovec([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +[<<1,2,3,1,2,3,4,5,4,6>>]
    │ │ │  %% If you pass larger binaries, they are split and returned in a form
    │ │ │  %% optimized for calling the C function writev.
    │ │ │ -> erlang:iolist_to_iovec([<<1>>,<<2:8096>>,<<3:8096>>]).
    │ │ │ -[<<1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    │ │ │ -   0,...>>,
    │ │ │ - <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    │ │ │ -   ...>>,
    │ │ │ - <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...>>]
    │ │ │ +>
    erlang:iolist_to_iovec([<<1>>,<<2:8096>>,<<3:8096>>]). │ │ │ +[<<1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, │ │ │ + 0,...>>, │ │ │ + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, │ │ │ + ...>>, │ │ │ + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10253,19 +10253,19 @@ │ │ │
    │ │ │ │ │ │
    -spec is_map_key(Key, Map) -> boolean() when Key :: term(), Map :: map().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns true if map Map contains Key and returns false if it does not │ │ │ -contain the Key.

    The call fails with a {badmap,Map} exception if Map is not a map.

    Example:

    > Map = #{"42" => value}.
    │ │ │ -#{"42" => value}
    │ │ │ -> is_map_key("42",Map).
    │ │ │ +contain the Key.

    The call fails with a {badmap,Map} exception if Map is not a map.

    Example:

    > Map = #{"42" => value}.
    │ │ │ +#{"42" => value}
    │ │ │ +> is_map_key("42",Map).
    │ │ │  true
    │ │ │ -> is_map_key(value,Map).
    │ │ │ +> is_map_key(value,Map).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10508,15 +10508,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec length(List) -> non_neg_integer() when List :: [term()].
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the length of List.

    For example:

    > length([1,2,3,4,5,6,7,8,9]).
    │ │ │ +

    Returns the length of List.

    For example:

    > length([1,2,3,4,5,6,7,8,9]).
    │ │ │  9
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10546,15 +10546,15 @@ │ │ │ Unicode characters above 255.

    Note

    The number of characters that are permitted in an atom name is limited. The │ │ │ default limits can be found in the │ │ │ efficiency guide (section System Limits).

    Note

    There is a configurable limit │ │ │ on how many atoms that can exist and atoms are not │ │ │ garbage collected. Therefore, it is recommended to consider if │ │ │ list_to_existing_atom/1 is a better option than │ │ │ list_to_atom/1. The default limits can be found in the │ │ │ -Efficiency Guide (section System Limits).

    Example:

    > list_to_atom("Erlang").
    │ │ │ +Efficiency Guide (section System Limits).

    Example:

    > list_to_atom("Erlang").
    │ │ │  'Erlang'
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10575,22 +10575,22 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_binary(IoList) -> binary() when IoList :: iolist().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a binary that is made from the integers and binaries in IoList.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -> list_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │ +

    Returns a binary that is made from the integers and binaries in IoList.

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +> list_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a bitstring that is made from the integers and bitstrings in │ │ │ BitstringList. (The last tail in BitstringList is allowed to be a │ │ │ -bitstring.)

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -> Bin3 = <<6,7:4>>.
    │ │ │ -<<6,7:4>>
    │ │ │ -> list_to_bitstring([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6,7:4>>
    │ │ │ +bitstring.)

    For example:

    > Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +> Bin3 = <<6,7:4>>.
    │ │ │ +<<6,7:4>>
    │ │ │ +> list_to_bitstring([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6,7:4>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10683,15 +10683,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_float(String) -> float() when String :: string().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the float whose text representation is String.

    For example:

    > list_to_float("2.2017764e+0").
    │ │ │ +

    Returns the float whose text representation is String.

    For example:

    > list_to_float("2.2017764e+0").
    │ │ │  2.2017764

    The float string format is the same as the format for │ │ │ Erlang float literals except for that underscores │ │ │ are not permitted.

    Failure: badarg if String contains a bad representation of a float.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10714,17 +10714,17 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_integer(String) -> integer() when String :: string().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer whose text representation is String.

    For example:

    > list_to_integer("123").
    │ │ │ -123
    > list_to_integer("-123").
    │ │ │ --123
    > list_to_integer("+123234982304982309482093833234234").
    │ │ │ +

    Returns an integer whose text representation is String.

    For example:

    > list_to_integer("123").
    │ │ │ +123
    > list_to_integer("-123").
    │ │ │ +-123
    > list_to_integer("+123234982304982309482093833234234").
    │ │ │  123234982304982309482093833234234

    String must contain at least one digit character and can have an optional │ │ │ prefix consisting of a single "+" or "-" character (that is, String must │ │ │ match the regular expression "^[+-]?[0-9]+$").

    Failure: badarg if String contains a bad representation of an integer.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10747,19 +10747,19 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_integer(String, Base) -> integer() when String :: string(), Base :: 2..36.
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer whose text representation in base Base is String.

    For example:

    > list_to_integer("3FF", 16).
    │ │ │ -1023
    > list_to_integer("+3FF", 16).
    │ │ │ -1023
    > list_to_integer("3ff", 16).
    │ │ │ -1023
    > list_to_integer("3fF", 16).
    │ │ │ -1023
    > list_to_integer("-3FF", 16).
    │ │ │ +

    Returns an integer whose text representation in base Base is String.

    For example:

    > list_to_integer("3FF", 16).
    │ │ │ +1023
    > list_to_integer("+3FF", 16).
    │ │ │ +1023
    > list_to_integer("3ff", 16).
    │ │ │ +1023
    > list_to_integer("3fF", 16).
    │ │ │ +1023
    > list_to_integer("-3FF", 16).
    │ │ │  -1023

    For example, when Base is 16, String must match the regular expression │ │ │ "^[+-]?([0-9]|[A-F]|[a-f])+$".

    Failure: badarg if String contains a bad representation of an integer.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10781,15 +10781,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_pid(String) -> pid() when String :: string().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a process identifier whose text representation is a String.

    For example:

    > list_to_pid("<0.4.1>").
    │ │ │ +

    Returns a process identifier whose text representation is a String.

    For example:

    > list_to_pid("<0.4.1>").
    │ │ │  <0.4.1>

    Failure: badarg if String contains a bad representation of a process │ │ │ identifier.

    Warning

    This BIF is intended for debugging and is not to be used in application │ │ │ programs.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10814,15 +10814,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_port(String) -> port() when String :: string().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a port identifier whose text representation is a String.

    For example:

    > list_to_port("#Port<0.4>").
    │ │ │ +

    Returns a port identifier whose text representation is a String.

    For example:

    > list_to_port("#Port<0.4>").
    │ │ │  #Port<0.4>

    Failure: badarg if String contains a bad representation of a port │ │ │ identifier.

    Warning

    This BIF is intended for debugging and is not to be used in application │ │ │ programs.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10847,15 +10847,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_ref(String) -> reference() when String :: string().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a reference whose text representation is a String.

    For example:

    > list_to_ref("#Ref<0.4192537678.4073193475.71181>").
    │ │ │ +

    Returns a reference whose text representation is a String.

    For example:

    > list_to_ref("#Ref<0.4192537678.4073193475.71181>").
    │ │ │  #Ref<0.4192537678.4073193475.71181>

    Failure: badarg if String contains a bad representation of a reference.

    Warning

    This BIF is intended for debugging and is not to be used in application │ │ │ programs.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10877,16 +10877,16 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec list_to_tuple(List) -> tuple() when List :: [term()].
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a tuple corresponding to List, for example

    > list_to_tuple([share, ['Ericsson_B', 163]]).
    │ │ │ -{share, ['Ericsson_B', 163]}

    List can contain any Erlang terms.

    │ │ │ +

    Returns a tuple corresponding to List, for example

    > list_to_tuple([share, ['Ericsson_B', 163]]).
    │ │ │ +{share, ['Ericsson_B', 163]}

    List can contain any Erlang terms.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10936,16 +10936,16 @@ │ │ │
    │ │ │ │ │ │
    -spec make_tuple(Arity, InitialValue) -> tuple() when Arity :: arity(), InitialValue :: term().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Creates a new tuple of the specified Arity, where all elements are │ │ │ -InitialValue.

    For example:

    > erlang:make_tuple(4, []).
    │ │ │ -{[],[],[],[]}
    │ │ │ +InitialValue.

    For example:

    > erlang:make_tuple(4, []).
    │ │ │ +{[],[],[],[]}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Creates a tuple of size Arity, where each element has value DefaultValue, │ │ │ and then fills in values from InitList.

    Each list element in InitList must be a two-tuple, where the first element is │ │ │ a position in the newly created tuple and the second element is any term. If a │ │ │ position occurs more than once in the list, the term corresponding to the last │ │ │ -occurrence is used.

    For example:

    > erlang:make_tuple(5, [], [{2,ignored},{5,zz},{2,aa}]).
    │ │ │ -{[],aa,[],[],zz}
    │ │ │ +occurrence is used.

    For example:

    > erlang:make_tuple(5, [], [{2,ignored},{5,zz},{2,aa}]).
    │ │ │ +{[],aa,[],[],zz}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns value Value associated with Key if Map contains Key.

    The call fails with a {badmap,Map} exception if Map is not a map, or with a │ │ │ {badkey,Key} exception if no value is associated with Key.

    Example:

    > Key = 1337,
    │ │ │ -  Map = #{42 => value_two,1337 => "value one","a" => 1},
    │ │ │ -  map_get(Key,Map).
    │ │ │ +  Map = #{42 => value_two,1337 => "value one","a" => 1},
    │ │ │ +  map_get(Key,Map).
    │ │ │  "value one"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11040,15 +11040,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

    Returns an integer, which is the number of key-value pairs in Map.

    For example:

    > map_size(#{a=>1, b=>2, c=>3}).
    │ │ │ +

    Returns an integer, which is the number of key-value pairs in Map.

    For example:

    > map_size(#{a=>1, b=>2, c=>3}).
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns the largest of Term1 and Term2. If the terms compare equal with the │ │ │ == operator, Term1 is returned.

    The Expressions section contains │ │ │ -descriptions of the == operator and how terms are ordered.

    Examples:

    > max(1, 2).
    │ │ │ -2
    > max(1.0, 1).
    │ │ │ -1.0
    > max(1, 1.0).
    │ │ │ -1
    > max("abc", "b").
    │ │ │ +descriptions of the == operator and how terms are ordered.

    Examples:

    > max(1, 2).
    │ │ │ +2
    > max(1.0, 1).
    │ │ │ +1.0
    > max(1, 1.0).
    │ │ │ +1
    > max("abc", "b").
    │ │ │  "b"

    Change

    Allowed in guards tests from Erlang/OTP 26.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns the smallest of Term1 and Term2. If the terms compare equal with the │ │ │ == operator, Term1 is returned.

    The Expressions section contains │ │ │ -descriptions of the == operator and how terms are ordered.

    Examples:

    > min(1, 2).
    │ │ │ -1
    > min(1.0, 1).
    │ │ │ -1.0
    > min(1, 1.0).
    │ │ │ -1
    > min("abc", "b").
    │ │ │ +descriptions of the == operator and how terms are ordered.

    Examples:

    > min(1, 2).
    │ │ │ +1
    > min(1.0, 1).
    │ │ │ +1.0
    > min(1, 1.0).
    │ │ │ +1
    > min("abc", "b").
    │ │ │  "abc"

    Change

    Allowed in guards tests from Erlang/OTP 26.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11276,15 +11276,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec pid_to_list(Pid) -> string() when Pid :: pid().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns a string corresponding to the text representation of Pid.

    For example:

    > erlang:pid_to_list(self()).
    │ │ │ +

    Returns a string corresponding to the text representation of Pid.

    For example:

    > erlang:pid_to_list(self()).
    │ │ │  "<0.85.0>"

    Note

    The creation for the node is not included in the list │ │ │ representation of Pid. This means that processes in different incarnations │ │ │ of a node with a specific name can get the same list representation.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11367,18 +11367,18 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec round(Number) -> integer() when Number :: number().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer by rounding Number.

    For example:

    round(42.1).
    │ │ │ -42
    round(5.5).
    │ │ │ -6
    round(-5.5).
    │ │ │ --6
    round(36028797018963969.0).
    │ │ │ +

    Returns an integer by rounding Number.

    For example:

    round(42.1).
    │ │ │ +42
    round(5.5).
    │ │ │ +6
    round(-5.5).
    │ │ │ +-6
    round(36028797018963969.0).
    │ │ │  36028797018963968

    In the last example, round(36028797018963969.0) evaluates to │ │ │ 36028797018963968. The reason for this is that the number │ │ │ 36028797018963969.0 cannot be represented exactly as a float value. Instead, │ │ │ the float literal is represented as 36028797018963968.0, which is the closest │ │ │ number that can be represented exactly as a float value. See │ │ │ Representation of Floating Point Numbers │ │ │ for additional information.

    │ │ │ @@ -11408,16 +11408,16 @@ │ │ │
    -spec setelement(Index, Tuple1, Value) -> Tuple2
    │ │ │                      when Index :: pos_integer(), Tuple1 :: tuple(), Tuple2 :: tuple(), Value :: term().
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a tuple that is a copy of argument Tuple1 with the element specified │ │ │ by integer argument Index (the first element is the element with index 1) │ │ │ -replaced by argument Value.

    For example:

    > setelement(2, {10, green, bottles}, red).
    │ │ │ -{10,red,bottles}
    │ │ │ +replaced by argument Value.

    For example:

    > setelement(2, {10, green, bottles}, red).
    │ │ │ +{10,red,bottles}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11440,17 +11440,17 @@ │ │ │
    │ │ │ │ │ │
    -spec size(Item) -> non_neg_integer() when Item :: tuple() | binary().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns the number of elements in a tuple or the number of bytes in a binary or │ │ │ -bitstring.

    For example:

    > size({morni, mulle, bwange}).
    │ │ │ +bitstring.

    For example:

    > size({morni, mulle, bwange}).
    │ │ │  3
    │ │ │ -> size(<<11, 22, 33>>).
    │ │ │ +> size(<<11, 22, 33>>).
    │ │ │  3

    For bitstrings, the number of whole bytes is returned. That is, if the number of │ │ │ bits in the bitstring is not divisible by 8, the resulting number of bytes is │ │ │ rounded down.

    See also tuple_size/1, byte_size/1, and bit_size/1.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11474,23 +11474,23 @@ │ │ │
    │ │ │ │ │ │
    -spec split_binary(Bin, Pos) -> {binary(), binary()} when Bin :: binary(), Pos :: non_neg_integer().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a tuple containing the binaries that are the result of splitting Bin │ │ │ -into two parts at position Pos.

    This is not a destructive operation. After the operation, there are three binaries altogether.

    For example:

    > B = list_to_binary("0123456789").
    │ │ │ -<<"0123456789">>
    │ │ │ -> byte_size(B).
    │ │ │ +into two parts at position Pos.

    This is not a destructive operation. After the operation, there are three binaries altogether.

    For example:

    > B = list_to_binary("0123456789").
    │ │ │ +<<"0123456789">>
    │ │ │ +> byte_size(B).
    │ │ │  10
    │ │ │ -> {B1, B2} = split_binary(B,3).
    │ │ │ -{<<"012">>,<<"3456789">>}
    │ │ │ -> byte_size(B1).
    │ │ │ +> {B1, B2} = split_binary(B,3).
    │ │ │ +{<<"012">>,<<"3456789">>}
    │ │ │ +> byte_size(B1).
    │ │ │  3
    │ │ │ -> byte_size(B2).
    │ │ │ +> byte_size(B2).
    │ │ │  7
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns a binary data object that is the result of encoding Term according to │ │ │ the Erlang external term format.

    This can be used for various purposes, for example, writing a term to a file in │ │ │ an efficient way, or sending an Erlang term to some type of communications │ │ │ -channel not supported by distributed Erlang.

    > Bin = term_to_binary(hello).
    │ │ │ -<<131,100,0,5,104,101,108,108,111>>
    │ │ │ -> hello = binary_to_term(Bin).
    │ │ │ +channel not supported by distributed Erlang.

    > Bin = term_to_binary(hello).
    │ │ │ +<<131,100,0,5,104,101,108,108,111>>
    │ │ │ +> hello = binary_to_term(Bin).
    │ │ │  hello

    See also binary_to_term/1.

    Note

    There is no guarantee that this function will return the same encoded │ │ │ representation for the same term.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -11741,18 +11741,18 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec tl(List) -> Tail when List :: nonempty_maybe_improper_list(), Tail :: term().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns the tail of List, that is, the list minus the first element

    It works with improper lists.

    Examples:

    > tl([geesties, guilies, beasties]).
    │ │ │ -[guilies, beasties]
    > tl([geesties]).
    │ │ │ -[]
    > tl([geesties, guilies, beasties | improper_end]).
    │ │ │ -[guilies, beasties | improper_end]
    > tl([geesties | improper_end]).
    │ │ │ +

    Returns the tail of List, that is, the list minus the first element

    It works with improper lists.

    Examples:

    > tl([geesties, guilies, beasties]).
    │ │ │ +[guilies, beasties]
    > tl([geesties]).
    │ │ │ +[]
    > tl([geesties, guilies, beasties | improper_end]).
    │ │ │ +[guilies, beasties | improper_end]
    > tl([geesties | improper_end]).
    │ │ │  improper_end

    Failure: badarg if List is an empty list [].

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11775,18 +11775,18 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec trunc(Number) -> integer() when Number :: number().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Truncates the decimals of Number.

    For example:

    > trunc(5.7).
    │ │ │ -5
    > trunc(-5.7).
    │ │ │ --5
    > trunc(5).
    │ │ │ -5
    > trunc(36028797018963969.0).
    │ │ │ +

    Truncates the decimals of Number.

    For example:

    > trunc(5.7).
    │ │ │ +5
    > trunc(-5.7).
    │ │ │ +-5
    > trunc(5).
    │ │ │ +5
    > trunc(36028797018963969.0).
    │ │ │  36028797018963968

    In the last example, trunc(36028797018963969.0) evaluates to │ │ │ 36028797018963968. The reason for this is that the number │ │ │ 36028797018963969.0 cannot be represented exactly as a float value. Instead, │ │ │ the float literal is represented as 36028797018963968.0, which is the closest │ │ │ number that can be represented exactly as a float value. See │ │ │ Representation of Floating Point Numbers │ │ │ for additional information.

    │ │ │ @@ -11815,15 +11815,15 @@ │ │ │ │ │ │
    │ │ │ │ │ │
    -spec tuple_size(Tuple) -> non_neg_integer() when Tuple :: tuple().
    │ │ │ │ │ │
    │ │ │ │ │ │ -

    Returns an integer that is the number of elements in Tuple.

    For example:

    > tuple_size({morni, mulle, bwange}).
    │ │ │ +

    Returns an integer that is the number of elements in Tuple.

    For example:

    > tuple_size({morni, mulle, bwange}).
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11845,16 +11845,16 @@ │ │ │
    │ │ │ │ │ │
    -spec tuple_to_list(Tuple) -> [term()] when Tuple :: tuple().
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns a list corresponding to Tuple. Tuple can contain any Erlang terms. │ │ │ -Example:

    > tuple_to_list({share, {'Ericsson_B', 163}}).
    │ │ │ -[share,{'Ericsson_B',163}]
    │ │ │ +Example:

    > tuple_to_list({share, {'Ericsson_B', 163}}).
    │ │ │ +[share,{'Ericsson_B',163}]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -12009,35 +12009,35 @@ │ │ │ │ │ │

    Create an alias which can be used when sending messages to the process that │ │ │ created the alias. When the alias has been deactivated, messages sent using the │ │ │ alias will be dropped. An alias can be deactivated using unalias/1.

    Currently available options for alias/1:

    • explicit_unalias - The alias can only be deactivated via a call to │ │ │ unalias/1. This is also the default behaviour if no options │ │ │ are passed or if alias/0 is called.

    • reply - The alias will be automatically deactivated when a reply message │ │ │ sent via the alias is received. The alias can also still be deactivated via a │ │ │ -call to unalias/1.

    Example:

    server() ->
    │ │ │ +call to unalias/1.

    Example:

    server() ->
    │ │ │      receive
    │ │ │ -        {request, AliasReqId, Request} ->
    │ │ │ -            Result = perform_request(Request),
    │ │ │ -            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │ +        {request, AliasReqId, Request} ->
    │ │ │ +            Result = perform_request(Request),
    │ │ │ +            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │      end,
    │ │ │ -    server().
    │ │ │ +    server().
    │ │ │  
    │ │ │ -client(ServerPid, Request) ->
    │ │ │ -    AliasReqId = alias([reply]),
    │ │ │ -    ServerPid ! {request, AliasReqId, Request},
    │ │ │ +client(ServerPid, Request) ->
    │ │ │ +    AliasReqId = alias([reply]),
    │ │ │ +    ServerPid ! {request, AliasReqId, Request},
    │ │ │      %% Alias will be automatically deactivated if we receive a reply
    │ │ │      %% since we used the 'reply' option...
    │ │ │      receive
    │ │ │ -        {reply, AliasReqId, Result} -> Result
    │ │ │ +        {reply, AliasReqId, Result} -> Result
    │ │ │      after 5000 ->
    │ │ │ -            unalias(AliasReqId),
    │ │ │ +            unalias(AliasReqId),
    │ │ │              %% Flush message queue in case the reply arrived
    │ │ │              %% just before the alias was deactivated...
    │ │ │ -            receive {reply, AliasReqId, Result} -> Result
    │ │ │ -            after 0 -> exit(timeout)
    │ │ │ +            receive {reply, AliasReqId, Result} -> Result
    │ │ │ +            after 0 -> exit(timeout)
    │ │ │              end
    │ │ │      end.

    Note that both the server and the client in this example must be executing on at │ │ │ least OTP 24 systems in order for this to work.

    For more information on process aliases see the │ │ │ Process Aliases section of │ │ │ the Erlang Reference Manual.

    │ │ │
    │ │ │ │ │ │ @@ -12096,17 +12096,17 @@ │ │ │
    -spec apply(Module, Function, Args) -> term()
    │ │ │                 when Module :: module(), Function :: atom(), Args :: [term()].
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns the result of applying Function in Module to Args. The applied │ │ │ function must be exported from Module. The arity of the function is the length │ │ │ -of Args.

    For example:

    > apply(lists, reverse, [[a, b, c]]).
    │ │ │ -[c,b,a]
    │ │ │ -> apply(erlang, atom_to_list, ['Erlang']).
    │ │ │ +of Args.

    For example:

    > apply(lists, reverse, [[a, b, c]]).
    │ │ │ +[c,b,a]
    │ │ │ +> apply(erlang, atom_to_list, ['Erlang']).
    │ │ │  "Erlang"

    If the number of arguments are known at compile time, the call is better written │ │ │ as Module:Function(Arg1, Arg2, ..., ArgN).

    Failure: error_handler:undefined_function/3 is called if the applied function │ │ │ is not exported. The error handler can be redefined (see process_flag/2). If │ │ │ error_handler is undefined, or if the user has redefined the default │ │ │ error_handler so the replacement module is undefined, an error with reason │ │ │ undef is generated.

    │ │ │ │ │ │ @@ -12213,17 +12213,17 @@ │ │ │ when MonitorRef :: reference(), OptionList :: [Option], Option :: flush | info.
    │ │ │ │ │ │ │ │ │ │ │ │

    The returned value is true unless info is part of OptionList.

    demonitor(MonitorRef, []) is equivalent to │ │ │ demonitor(MonitorRef).

    Options:

    • flush - Removes (one) {_, MonitorRef, _, _, _} message, if there is │ │ │ one, from the caller message queue after monitoring has been stopped.

      Calling demonitor(MonitorRef, [flush]) is equivalent to the │ │ │ -following, but more efficient:

      demonitor(MonitorRef),
      │ │ │ +following, but more efficient:

      demonitor(MonitorRef),
      │ │ │  receive
      │ │ │ -    {_, MonitorRef, _, _, _} ->
      │ │ │ +    {_, MonitorRef, _, _, _} ->
      │ │ │          true
      │ │ │  after 0 ->
      │ │ │          true
      │ │ │  end
    • info - The returned value is one of the following:

      • true - The monitor was found and removed. In this case, no 'DOWN' │ │ │ message corresponding to this monitor has been delivered and will not be │ │ │ delivered.

      • false - The monitor was not found and could not be removed. This │ │ │ probably because someone already has placed a 'DOWN' message corresponding │ │ │ @@ -12252,18 +12252,18 @@ │ │ │ │ │ │

        │ │ │ │ │ │
        -spec erase() -> [{Key, Val}] when Key :: term(), Val :: term().
        │ │ │ │ │ │
        │ │ │ │ │ │ -

        Returns the process dictionary and deletes it.

        For example:

        > put(key1, {1, 2, 3}),
        │ │ │ -put(key2, [a, b, c]),
        │ │ │ -erase().
        │ │ │ -[{key1,{1,2,3}},{key2,[a,b,c]}]
        │ │ │ +

        Returns the process dictionary and deletes it.

        For example:

        > put(key1, {1, 2, 3}),
        │ │ │ +put(key2, [a, b, c]),
        │ │ │ +erase().
        │ │ │ +[{key1,{1,2,3}},{key2,[a,b,c]}]
        │ │ │ │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ │ │ │

        Returns the value Val associated with Key and deletes it from the process │ │ │ dictionary. Returns undefined if no value is associated with Key.

        The average time complexity for the current implementation of this function is │ │ │ O(1) and the worst case time complexity is O(N), where N is the number of │ │ │ -items in the process dictionary.

        For example:

        > put(key1, {merry, lambs, are, playing}),
        │ │ │ -X = erase(key1),
        │ │ │ -{X, erase(key1)}.
        │ │ │ -{{merry,lambs,are,playing},undefined}
        │ │ │ +items in the process dictionary.

        For example:

        > put(key1, {merry, lambs, are, playing}),
        │ │ │ +X = erase(key1),
        │ │ │ +{X, erase(key1)}.
        │ │ │ +{{merry,lambs,are,playing},undefined}
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ │ │ │

        Raises an exception of class error with the reason Reason.

        As evaluating this function causes an exception to be thrown, it has no return value.

        The intent of the exception class error is to signal that an unexpected error │ │ │ has happened (for example, a function is called with a parameter that has an │ │ │ incorrect type). See the guide about │ │ │ errors and error handling for additional information. │ │ │ -Example:

        > catch error(foobar).
        │ │ │ -{'EXIT',{foobar,[{shell,apply_fun,3,
        │ │ │ -                        [{file,"shell.erl"},{line,906}]},
        │ │ │ -                 {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,677}]},
        │ │ │ -                 {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,430}]},
        │ │ │ -                 {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
        │ │ │ -                 {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
        │ │ │ -                 {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
        │ │ │ +Example:

        > catch error(foobar).
        │ │ │ +{'EXIT',{foobar,[{shell,apply_fun,3,
        │ │ │ +                        [{file,"shell.erl"},{line,906}]},
        │ │ │ +                 {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,677}]},
        │ │ │ +                 {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,430}]},
        │ │ │ +                 {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
        │ │ │ +                 {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
        │ │ │ +                 {shell,eval_loop,3,[{file,"shell.erl"},{line,627}]}]}}
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ @@ -12365,21 +12365,21 @@ │ │ │ none.

        If Args is a list, it is used to provide the arguments for the current │ │ │ function in the stack back-trace. If it is none, the arity of the calling │ │ │ function is used in the stacktrace. As evaluating this function causes an │ │ │ exception to be raised, it has no return value.

        The intent of the exception class error is to signal that an unexpected error │ │ │ has happened (for example, a function is called with a parameter that has an │ │ │ incorrect type). See the guide about │ │ │ errors and error handling for additional information. │ │ │ -Example:

        test.erl:

        -module(test).
        │ │ │ --export([example_fun/2]).
        │ │ │ +Example:

        test.erl:

        -module(test).
        │ │ │ +-export([example_fun/2]).
        │ │ │  
        │ │ │ -example_fun(A1, A2) ->
        │ │ │ -    erlang:error(my_error, [A1, A2]).

        Erlang shell:

        6> c(test).
        │ │ │ -{ok,test}
        │ │ │ -7> test:example_fun(arg1,"this is the second argument").
        │ │ │ +example_fun(A1, A2) ->
        │ │ │ +    erlang:error(my_error, [A1, A2]).

        Erlang shell:

        6> c(test).
        │ │ │ +{ok,test}
        │ │ │ +7> test:example_fun(arg1,"this is the second argument").
        │ │ │  ** exception error: my_error
        │ │ │       in function  test:example_fun/2
        │ │ │           called as test:example_fun(arg1,"this is the second argument")
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ @@ -12456,18 +12456,18 @@ │ │ │ │ │ │ │ │ │ │ │ │

        Raises an exception of class exit with exit reason Reason.

        As evaluating this function causes an exception to be raised, it has no return value.

        The intent of the exception class exit is that the current process should be │ │ │ stopped (for example when a message telling a process to stop is received).

        This function differ from error/1,2,3 by causing an exception of │ │ │ a different class and by having a reason that does not include the list of │ │ │ functions from the call stack.

        See the guide about errors and error handling for │ │ │ -additional information.

        Example:

        > exit(foobar).
        │ │ │ +additional information.

        Example:

        > exit(foobar).
        │ │ │  ** exception exit: foobar
        │ │ │ -> catch exit(foobar).
        │ │ │ -{'EXIT',foobar}

        Note

        If a process calls exit(kill) and does not catch the exception, │ │ │ +> catch exit(foobar). │ │ │ +{'EXIT',foobar}

        Note

        If a process calls exit(kill) and does not catch the exception, │ │ │ it will terminate with exit reason kill and also emit exit signals with exit │ │ │ reason kill (not killed) to all linked processes. Such exit signals with │ │ │ exit reason kill can be trapped by the linked processes. Note that this │ │ │ means that signals with exit reason kill behave differently depending on how │ │ │ they are sent because the signal will be untrappable if a process sends such a │ │ │ signal to another process with erlang:exit/2.

        │ │ │
        │ │ │ @@ -12660,19 +12660,19 @@ │ │ │
        │ │ │ │ │ │
        -spec get() -> [{Key, Val}] when Key :: term(), Val :: term().
        │ │ │ │ │ │
        │ │ │ │ │ │

        Returns the process dictionary as a list of {Key, Val} tuples. The items in │ │ │ -the returned list can be in any order.

        For example:

        > put(key1, merry),
        │ │ │ -put(key2, lambs),
        │ │ │ -put(key3, {are, playing}),
        │ │ │ -get().
        │ │ │ -[{key1,merry},{key2,lambs},{key3,{are,playing}}]
        │ │ │ +the returned list can be in any order.

        For example:

        > put(key1, merry),
        │ │ │ +put(key2, lambs),
        │ │ │ +put(key3, {are, playing}),
        │ │ │ +get().
        │ │ │ +[{key1,merry},{key2,lambs},{key3,{are,playing}}]
        │ │ │ │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ │ │ │

        Returns the value Val associated with Key in the process dictionary, or │ │ │ undefined if Key does not exist.

        The expected time complexity for the current implementation of this function is │ │ │ O(1) and the worst case time complexity is O(N), where N is the number of │ │ │ -items in the process dictionary.

        For example:

        > put(key1, merry),
        │ │ │ -put(key2, lambs),
        │ │ │ -put({any, [valid, term]}, {are, playing}),
        │ │ │ -get({any, [valid, term]}).
        │ │ │ -{are,playing}
        │ │ │ +items in the process dictionary.

        For example:

        > put(key1, merry),
        │ │ │ +put(key2, lambs),
        │ │ │ +put({any, [valid, term]}, {are, playing}),
        │ │ │ +get({any, [valid, term]}).
        │ │ │ +{are,playing}
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ @@ -12730,19 +12730,19 @@ │ │ │ │ │ │ │ │ │

        Returns a list of all keys present in the process dictionary. The items in the │ │ │ -returned list can be in any order.

        For example:

        > put(dog, {animal,1}),
        │ │ │ -put(cow, {animal,2}),
        │ │ │ -put(lamb, {animal,3}),
        │ │ │ -get_keys().
        │ │ │ -[dog,cow,lamb]
        │ │ │ +returned list can be in any order.

        For example:

        > put(dog, {animal,1}),
        │ │ │ +put(cow, {animal,2}),
        │ │ │ +put(lamb, {animal,3}),
        │ │ │ +get_keys().
        │ │ │ +[dog,cow,lamb]
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ @@ -12763,22 +12763,22 @@ │ │ │ │ │ │ │ │ │

        Returns a list of keys that are associated with the value Val in the process │ │ │ -dictionary. The items in the returned list can be in any order.

        For example:

        > put(mary, {1, 2}),
        │ │ │ -put(had, {1, 2}),
        │ │ │ -put(a, {1, 2}),
        │ │ │ -put(little, {1, 2}),
        │ │ │ -put(dog, {1, 3}),
        │ │ │ -put(lamb, {1, 2}),
        │ │ │ -get_keys({1, 2}).
        │ │ │ -[mary,had,a,little,lamb]
        │ │ │ +dictionary. The items in the returned list can be in any order.

        For example:

        > put(mary, {1, 2}),
        │ │ │ +put(had, {1, 2}),
        │ │ │ +put(a, {1, 2}),
        │ │ │ +put(little, {1, 2}),
        │ │ │ +put(dog, {1, 3}),
        │ │ │ +put(lamb, {1, 2}),
        │ │ │ +get_keys({1, 2}).
        │ │ │ +[mary,had,a,little,lamb]
        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ │ │ │ @@ -12925,17 +12925,17 @@ │ │ │

        Pid must refer to a process at the local node.

        Returns true if the process exists and is alive, that is, is not exiting and │ │ │ has not exited. Otherwise returns false.

        If process P1 calls is_process_alive(P2Pid) it is │ │ │ guaranteed that all signals, sent from P1 to P2 (P2 is the process with │ │ │ identifier P2Pid) before the call, will be delivered to P2 before the │ │ │ aliveness of P2 is checked. This guarantee means that one can use │ │ │ is_process_alive/1 to let a process P1 wait until a │ │ │ process P2, which has got an exit signal with reason kill from P1, is │ │ │ -killed.

        For example:

        exit(P2Pid, kill),
        │ │ │ +killed.

        For example:

        exit(P2Pid, kill),
        │ │ │  % P2 might not be killed
        │ │ │ -is_process_alive(P2Pid),
        │ │ │ +is_process_alive(P2Pid),
        │ │ │  % P2 is not alive (the call above always return false)

        See the documentation about signals │ │ │ and erlang:exit/2 for more information about signals and exit │ │ │ signals.

        │ │ │
        │ │ │ │ │ │
        │ │ │ │ │ │ @@ -13016,24 +13016,24 @@ │ │ │
        -spec monitor(process, monitor_process_identifier()) -> MonitorRef when MonitorRef :: reference();
        │ │ │               (port, monitor_port_identifier()) -> MonitorRef when MonitorRef :: reference();
        │ │ │               (time_offset, clock_service) -> MonitorRef when MonitorRef :: reference().
        │ │ │ │ │ │ │ │ │ │ │ │

        Sends a monitor request of type Type to the entity identified by Item.

        If the monitored entity does not exist or it changes monitored state, the caller │ │ │ -of monitor/2 is notified by a message on the following format:

        {Tag, MonitorRef, Type, Object, Info}

        Note

        The monitor request is an asynchronous signal. That is, it takes time before │ │ │ +of monitor/2 is notified by a message on the following format:

        {Tag, MonitorRef, Type, Object, Info}

        Note

        The monitor request is an asynchronous signal. That is, it takes time before │ │ │ the signal reaches its destination.

        Type can be one of the following atoms: process, port or time_offset.

        A process or port monitor is triggered only once, after that it is removed │ │ │ from both monitoring process and the monitored entity. Monitors are fired when │ │ │ the monitored process or port terminates, does not exist at the moment of │ │ │ creation, or if the connection to it is lost. If the connection to it is lost, │ │ │ we do not know if it still exists. The monitoring is also turned off when │ │ │ demonitor/1 is called.

        A process or port monitor by name resolves the RegisteredName to pid/0 │ │ │ or port/0 only once at the moment of monitor instantiation, later changes to │ │ │ the name registration will not affect the existing monitor.

        When a process or port monitor is triggered, a 'DOWN' message is sent that │ │ │ -has the following pattern:

        {'DOWN', MonitorRef, Type, Object, Info}

        In the monitor message MonitorRef and Type are the same as described │ │ │ +has the following pattern:

        {'DOWN', MonitorRef, Type, Object, Info}

        In the monitor message MonitorRef and Type are the same as described │ │ │ earlier, and:

        • Object - The monitored entity, which triggered the event. When │ │ │ monitoring a process or a local port, Object will be equal to the pid/0 │ │ │ or port/0 that was being monitored. When monitoring process or port by │ │ │ name, Object will have format {RegisteredName, Node} where │ │ │ RegisteredName is the name which has been used with │ │ │ monitor/2 call and Node is local or remote node name (for │ │ │ ports monitored by name, Node is always local node name).

        • Info - Either the exit reason of the process, noproc (process or port │ │ │ @@ -13069,15 +13069,15 @@ │ │ │ offset is changed when the runtime system detects that the │ │ │ OS system time has changed. The runtime │ │ │ system does, however, not detect this immediately when it occurs. A task │ │ │ checking the time offset is scheduled to execute at least once a minute, so │ │ │ under normal operation this is to be detected within a minute, but during │ │ │ heavy load it can take longer time.

          The monitor is not automatically removed after it has been triggered. That │ │ │ is, repeated changes of the time offset trigger the monitor repeatedly.

          When the monitor is triggered a 'CHANGE' message is sent to the monitoring │ │ │ -process. A 'CHANGE' message has the following pattern:

          {'CHANGE', MonitorRef, Type, Item, NewTimeOffset}

          where MonitorRef, Type, and Item are the same as described above, and │ │ │ +process. A 'CHANGE' message has the following pattern:

          {'CHANGE', MonitorRef, Type, Item, NewTimeOffset}

          where MonitorRef, Type, and Item are the same as described above, and │ │ │ NewTimeOffset is the new time offset.

          When the 'CHANGE' message has been received you are guaranteed not to │ │ │ retrieve the old time offset when calling │ │ │ erlang:time_offset/0. Notice that you can observe the │ │ │ change of the time offset when calling erlang:time_offset/0 before you get │ │ │ the 'CHANGE' message.

          Available since OTP 18.0.

        Making several calls to monitor/2 for the same Item and/or │ │ │ Type is not an error; it results in as many independent monitoring instances.

        The monitor functionality is expected to be extended. That is, other Types and │ │ │ Items are expected to be supported in a future release.

        Note

        If or when monitor/2 is extended, other possible values for │ │ │ @@ -13133,78 +13133,78 @@ │ │ │ via the alias is received. When a reply message is received via the alias │ │ │ the monitor will also be automatically removed. This is useful in │ │ │ client/server scenarios when a client monitors the server and will get the │ │ │ reply via the alias. Once the response is received both the alias and the │ │ │ monitor will be automatically removed regardless of whether the response is │ │ │ a reply or a 'DOWN' message. The alias can also still be deactivated via a │ │ │ call to unalias/1. Note that if the alias is removed using │ │ │ -the unalias/1 BIF, the monitor will still be left active.

      Example:

      server() ->
      │ │ │ +the unalias/1 BIF, the monitor will still be left active.

    Example:

    server() ->
    │ │ │      receive
    │ │ │ -        {request, AliasReqId, Request} ->
    │ │ │ -            Result = perform_request(Request),
    │ │ │ -            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │ +        {request, AliasReqId, Request} ->
    │ │ │ +            Result = perform_request(Request),
    │ │ │ +            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │      end,
    │ │ │ -    server().
    │ │ │ +    server().
    │ │ │  
    │ │ │ -client(ServerPid, Request) ->
    │ │ │ -    AliasMonReqId = monitor(process, ServerPid, [{alias, reply_demonitor}]),
    │ │ │ -    ServerPid ! {request, AliasMonReqId, Request},
    │ │ │ +client(ServerPid, Request) ->
    │ │ │ +    AliasMonReqId = monitor(process, ServerPid, [{alias, reply_demonitor}]),
    │ │ │ +    ServerPid ! {request, AliasMonReqId, Request},
    │ │ │      %% Alias as well as monitor will be automatically deactivated if we
    │ │ │      %% receive a reply or a 'DOWN' message since we used 'reply_demonitor'
    │ │ │      %% as unalias option...
    │ │ │      receive
    │ │ │ -        {reply, AliasMonReqId, Result} ->
    │ │ │ +        {reply, AliasMonReqId, Result} ->
    │ │ │              Result;
    │ │ │ -        {'DOWN', AliasMonReqId, process, ServerPid, ExitReason} ->
    │ │ │ -            error(ExitReason)
    │ │ │ +        {'DOWN', AliasMonReqId, process, ServerPid, ExitReason} ->
    │ │ │ +            error(ExitReason)
    │ │ │      end.

    Note that both the server and the client in this example must be executing on │ │ │ at least OTP 24 systems in order for this to work.

    For more information on process aliases see the │ │ │ Process Aliases section │ │ │ of the Erlang Reference Manual.

  • {tag, UserDefinedTag} - Replace the default Tag with UserDefinedTag │ │ │ in the monitor message delivered when the │ │ │ monitor is triggered. For example, when monitoring a process, the 'DOWN' tag │ │ │ in the down message will be replaced by UserDefinedTag.

    An example of how the {tag, UserDefinedTag} option can be used in order to │ │ │ enable the new │ │ │ selective receive optimization, │ │ │ -introduced in OTP 24, when making multiple requests to different servers:

    server() ->
    │ │ │ +introduced in OTP 24, when making multiple requests to different servers:

    server() ->
    │ │ │      receive
    │ │ │ -        {request, From, ReqId, Request} ->
    │ │ │ -            Result = perform_request(Request),
    │ │ │ -            From ! {reply, self(), ReqId, Result}
    │ │ │ +        {request, From, ReqId, Request} ->
    │ │ │ +            Result = perform_request(Request),
    │ │ │ +            From ! {reply, self(), ReqId, Result}
    │ │ │      end,
    │ │ │ -    server().
    │ │ │ +    server().
    │ │ │  
    │ │ │ -client(ServerPids, Request) when is_list(ServerPids) ->
    │ │ │ -    ReqId = make_ref(),
    │ │ │ -    lists:foreach(fun (ServerPid) ->
    │ │ │ -                          _ = monitor(process, ServerPid,
    │ │ │ -                                      [{tag, {'DOWN', ReqId}}]),
    │ │ │ -                          ServerPid ! {request, self(), ReqId, Request}
    │ │ │ +client(ServerPids, Request) when is_list(ServerPids) ->
    │ │ │ +    ReqId = make_ref(),
    │ │ │ +    lists:foreach(fun (ServerPid) ->
    │ │ │ +                          _ = monitor(process, ServerPid,
    │ │ │ +                                      [{tag, {'DOWN', ReqId}}]),
    │ │ │ +                          ServerPid ! {request, self(), ReqId, Request}
    │ │ │                    end,
    │ │ │ -                  ServerPids),
    │ │ │ -    receive_replies(ReqId, length(ServerPids), []).
    │ │ │ +                  ServerPids),
    │ │ │ +    receive_replies(ReqId, length(ServerPids), []).
    │ │ │  
    │ │ │ -receive_replies(_ReqId, 0, Acc) ->
    │ │ │ +receive_replies(_ReqId, 0, Acc) ->
    │ │ │      Acc;
    │ │ │ -receive_replies(ReqId, N, Acc) ->
    │ │ │ +receive_replies(ReqId, N, Acc) ->
    │ │ │      %% The compiler will detect that we match on the 'ReqId'
    │ │ │      %% reference in all clauses, and will enable the selective
    │ │ │      %% receive optimization which makes the receive able to
    │ │ │      %% skip past all messages present in the message queue at
    │ │ │      %% the time when the 'ReqId' reference was created...
    │ │ │      Res = receive
    │ │ │ -              {reply, ServerPid, ReqId, Result} ->
    │ │ │ +              {reply, ServerPid, ReqId, Result} ->
    │ │ │                    %% Here we typically would have deactivated the
    │ │ │                    %% monitor by a call to demonitor(Mon, [flush]) but
    │ │ │                    %% we ignore this in this example for simplicity...
    │ │ │ -                  {ok, ServerPid, Result};
    │ │ │ -              {{'DOWN', ReqId}, _Mon, process, ServerPid, ExitReason} ->
    │ │ │ -                  {error, ServerPid, ExitReason}
    │ │ │ +                  {ok, ServerPid, Result};
    │ │ │ +              {{'DOWN', ReqId}, _Mon, process, ServerPid, ExitReason} ->
    │ │ │ +                  {error, ServerPid, ExitReason}
    │ │ │            end,
    │ │ │ -    receive_replies(ReqId, N-1, [Res | Acc]).

    In order for this example to work as intended, the client must be executing on │ │ │ + receive_replies(ReqId, N-1, [Res | Acc]).

    In order for this example to work as intended, the client must be executing on │ │ │ at least an OTP 24 system, but the servers may execute on older systems.

  • │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -13910,15 +13910,15 @@ │ │ │ (sensitive, Boolean) -> OldBoolean when Boolean :: boolean(), OldBoolean :: boolean(); │ │ │ ({monitor_nodes, term()}, term()) -> term(); │ │ │ (monitor_nodes, term()) -> term().
    │ │ │ │ │ │ │ │ │ │ │ │

    Sets the process flag indicated to the specified value. Returns the previous value │ │ │ -of the flag.

    Flag is one of the following:

    • process_flag(async_dist, boolean())

      Enable or disable fully asynchronous distributed signaling for the calling │ │ │ +of the flag.

      Flag is one of the following:

      • process_flag(async_dist, boolean())

        Enable or disable fully asynchronous distributed signaling for the calling │ │ │ process. When disabled, which is the default, the process sending a distributed │ │ │ signal will block in the send operation if the buffer for the distribution │ │ │ channel reach the distribution buffer busy limit. The │ │ │ process will remain blocked until the buffer shrinks enough. This might in some │ │ │ cases take a substantial amount of time. When async_dist is enabled, send │ │ │ operations of distributed signals will always buffer the signal on the outgoing │ │ │ distribution channel and then immediately return. That is, these send operations │ │ │ @@ -13935,22 +13935,22 @@ │ │ │ caller.

        The async_dist flag can also be set on a new process when spawning it using │ │ │ the spawn_opt() BIF with the option │ │ │ {async_dist, Enable}. The default │ │ │ async_dist flag to use on newly spawned processes can be set by passing the │ │ │ command line argument +pad <boolean> when starting the │ │ │ runtime system. If the +pad <boolean> command line argument is not passed, the │ │ │ default value of the async_dist flag will be false.

        You can inspect the state of the async_dist process flag of a process by │ │ │ -calling process_info(Pid, async_dist).

      • process_flag(trap_exit, boolean())

        When trap_exit is set to true, exit signals arriving to a process are │ │ │ +calling process_info(Pid, async_dist).

      • process_flag(trap_exit, boolean())

        When trap_exit is set to true, exit signals arriving to a process are │ │ │ converted to {'EXIT', From, Reason} messages, which can be received as │ │ │ ordinary messages. If trap_exit is set to false, the process exits if it │ │ │ receives an exit signal other than normal and the exit signal is propagated to │ │ │ -its linked processes. Application processes are normally not to trap exits.

        See also exit/2.

      • process_flag(error_handler, module())

        Used by a process to redefine the error_handler for undefined function calls and │ │ │ +its linked processes. Application processes are normally not to trap exits.

        See also exit/2.

      • process_flag(error_handler, module())

        Used by a process to redefine the error_handler for undefined function calls and │ │ │ undefined registered processes. Use this flag with substantial caution, as code │ │ │ -auto-loading depends on the correct operation of the error handling module.

      • process_flag(fullsweep_after,  non_neg_integer())

        Changes the maximum number of generational collections before forcing a │ │ │ -fullsweep for the calling process.

      • process_flag(min_heap_size, non_neg_integer())

        Changes the minimum heap size for the calling process.

      • process_flag(min_bin_vheap_size, non_neg_integer())

        Changes the minimum binary virtual heap size for the calling process.

      • process_flag(max_heap_size, max_heap_size())

        This flag sets the maximum heap size for the calling process. If MaxHeapSize │ │ │ +auto-loading depends on the correct operation of the error handling module.

      • process_flag(fullsweep_after,  non_neg_integer())

        Changes the maximum number of generational collections before forcing a │ │ │ +fullsweep for the calling process.

      • process_flag(min_heap_size, non_neg_integer())

        Changes the minimum heap size for the calling process.

      • process_flag(min_bin_vheap_size, non_neg_integer())

        Changes the minimum binary virtual heap size for the calling process.

      • process_flag(max_heap_size, max_heap_size())

        This flag sets the maximum heap size for the calling process. If MaxHeapSize │ │ │ is an integer, the system default values for kill and error_logger are used.

        For details on how the heap grows, see │ │ │ Sizing the heap in the ERTS internal │ │ │ documentation.

        • size - The maximum size in words of the process. If set to zero, the │ │ │ heap size limit is disabled. badarg is be thrown if the value is smaller │ │ │ than min_heap_size. The size check │ │ │ is only done when a garbage collection is triggered.

          size is the entire heap of the process when garbage collection is triggered. │ │ │ This includes all generational heaps, the process stack, any │ │ │ @@ -13978,27 +13978,27 @@ │ │ │ of it is referred by the process.

          If include_shared_binaries is not defined in the map, the system default is │ │ │ used. The default system default is false. It can be changed by either the │ │ │ option +hmaxib in erl, or │ │ │ erlang:system_flag(max_heap_size, MaxHeapSize).

        The heap size of a process is quite hard to predict, especially the amount of │ │ │ memory that is used during the garbage collection. When contemplating using this │ │ │ option, it is recommended to first run it in production with kill set to │ │ │ false and inspect the log events to see what the normal peak sizes of the │ │ │ -processes in the system is and then tune the value accordingly.

      • process_flag(message_queue_data, message_queue_data())

        Determines how messages in the message queue are stored, as follows:

        • off_heap - All messages in the message queue will be stored outside │ │ │ +processes in the system is and then tune the value accordingly.

        • process_flag(message_queue_data, message_queue_data())

          Determines how messages in the message queue are stored, as follows:

          • off_heap - All messages in the message queue will be stored outside │ │ │ the process heap. This implies that no messages in the message queue will be │ │ │ part of a garbage collection of the process.

          • on_heap - All messages in the message queue will eventually be placed on │ │ │ the process heap. They can, however, be temporarily stored off the heap. This │ │ │ is how messages have always been stored up until ERTS 8.0.

          The default value of the message_queue_data process flag is determined by the │ │ │ command-line argument +hmqd in erl.

          If the process may potentially accumulate a large number of messages in its │ │ │ queue it is recommended to set the flag value to off_heap. This is due to the │ │ │ fact that the garbage collection of a process that has a large number of │ │ │ messages stored on the heap can become extremely expensive and the process can │ │ │ consume large amounts of memory. The performance of the actual message passing │ │ │ is, however, generally better when the flag value is on_heap.

          Changing the flag value causes any existing messages to be moved. The move │ │ │ operation is initiated, but not necessarily completed, by the time the function │ │ │ -returns.

        • process_flag(priority, priority_level())

          Sets the process priority. Level is an atom. Four priority levels exist: │ │ │ +returns.

        • process_flag(priority, priority_level())

          Sets the process priority. Level is an atom. Four priority levels exist: │ │ │ low, normal, high, and max. Default is normal.

          Note

          Priority level max is reserved for internal use in the Erlang runtime │ │ │ system, and is not to be used by others.

          Internally in each priority level, processes are scheduled in a round robin │ │ │ fashion.

          Execution of processes on priority normal and low are interleaved. Processes │ │ │ on priority low are selected for execution less frequently than processes on │ │ │ priority normal.

          When runnable processes on priority high exist, no processes on priority low │ │ │ or normal are selected for execution. Notice however that this does not mean │ │ │ that no processes on priority low or normal can run when processes are │ │ │ @@ -14019,24 +14019,24 @@ │ │ │ process during the call. Even if this is not the case with one version of the │ │ │ code that you have no control over, it can be the case in a future version of │ │ │ it. This can, for example, occur if a high priority process triggers code │ │ │ loading, as the code server runs on priority normal.

          Other priorities than normal are normally not needed. When other priorities │ │ │ are used, use them with care, especially priority high. A process on │ │ │ priority high is only to perform work for short periods. Busy looping for long │ │ │ periods in a high priority process causes most likely problems, as important │ │ │ -OTP servers run on priority normal.

        • process_flag(save_calls, 0..10000)

          N must be an integer in the interval 0..10000. If N > 0, call saving is made │ │ │ +OTP servers run on priority normal.

        • process_flag(save_calls, 0..10000)

          N must be an integer in the interval 0..10000. If N > 0, call saving is made │ │ │ active for the process. This means that information about the N most recent │ │ │ global function calls, BIF calls, sends, and receives made by the process are │ │ │ saved in a list, which can be retrieved with │ │ │ process_info(Pid, last_calls). A global function call is │ │ │ one in which the module of the function is explicitly mentioned. Only a fixed │ │ │ amount of information is saved, as follows:

          • A tuple {Module, Function, Arity} for function calls
          • The atoms send, 'receive', and timeout for sends and receives │ │ │ ('receive' when a message is received and timeout when a receive times │ │ │ out)

          If N = 0, call saving is disabled for the process, which is the default. │ │ │ -Whenever the size of the call saving list is set, its contents are reset.

        • process_flag(sensitive, boolean())

          Sets or clears flag sensitive for the current process. When a process has been │ │ │ +Whenever the size of the call saving list is set, its contents are reset.

        • process_flag(sensitive, boolean())

          Sets or clears flag sensitive for the current process. When a process has been │ │ │ marked as sensitive by calling │ │ │ process_flag(sensitive, true), features in the runtime │ │ │ system that can be used for examining the data or inner working of the process │ │ │ are silently disabled.

          Features that are disabled include (but are not limited to) the following:

          • Tracing. Trace flags can still be set for the process, but no trace messages │ │ │ of any kind are generated. (If flag sensitive is turned off, trace messages │ │ │ are again generated if any trace flags are set.)
          • Sequential tracing. The sequential trace token is propagated as usual, but no │ │ │ sequential trace messages are generated.

          process_info/1,2 cannot be used to read out the message queue or the process │ │ │ @@ -14280,16 +14280,16 @@ │ │ │ │ │ │ │ │ │ │ │ │

          Returns a list of process identifiers corresponding to all the processes │ │ │ currently existing on the local node.

          Notice that an exiting process exists, but is not alive. That is, │ │ │ is_process_alive/1 returns false for an exiting │ │ │ process, but its process identifier is part of the result returned from │ │ │ -processes/0.

          Example:

          > processes().
          │ │ │ -[<0.0.0>,<0.2.0>,<0.4.0>,<0.5.0>,<0.7.0>,<0.8.0>]
          │ │ │ +processes/0.

          Example:

          > processes().
          │ │ │ +[<0.0.0>,<0.2.0>,<0.4.0>,<0.5.0>,<0.7.0>,<0.8.0>]
          │ │ │ │ │ │ │ │ │
          │ │ │ │ │ │ │ │ │ │ │ │

          Adds a new Key to the process dictionary, associated with the value Val, and │ │ │ returns undefined. If Key exists, the old value is deleted and replaced by │ │ │ Val, and the function returns the old value.

          The average time complexity for the current implementation of this function is │ │ │ O(1) and the worst case time complexity is O(N), where N is the number of │ │ │ -items in the process dictionary.

          For example:

          > X = put(name, walrus), Y = put(name, carpenter),
          │ │ │ -Z = get(name),
          │ │ │ -{X, Y, Z}.
          │ │ │ -{undefined,walrus,carpenter}

          Note

          The values stored when put is evaluated within the scope of a catch are │ │ │ +items in the process dictionary.

          For example:

          > X = put(name, walrus), Y = put(name, carpenter),
          │ │ │ +Z = get(name),
          │ │ │ +{X, Y, Z}.
          │ │ │ +{undefined,walrus,carpenter}

          Note

          The values stored when put is evaluated within the scope of a catch are │ │ │ not retracted if a throw is evaluated, or if an error occurs.

          │ │ │
          │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ │ │ │

          Registers the name RegName with a process identifier (pid) or a port │ │ │ identifier in the │ │ │ name registry. │ │ │ RegName, which must be an atom, can be used instead of the pid or port │ │ │ identifier in send operator (RegName ! Message) and most other BIFs that take │ │ │ -a pid or port identifies as an argument.

          For example:

          > register(db, Pid).
          │ │ │ +a pid or port identifies as an argument.

          For example:

          > register(db, Pid).
          │ │ │  true

          The registered name is considered a │ │ │ Directly Visible Erlang Resource │ │ │ and is automatically unregistered when the process terminates.

          Failures:

          • badarg - If PidOrPort is not an existing local process or port.

          • badarg - If RegName is already in use.

          • badarg - If the process or port is already registered (already has a │ │ │ name).

          • badarg - If RegName is the atom undefined.

          │ │ │
          │ │ │ │ │ │
          │ │ │ @@ -14427,16 +14427,16 @@ │ │ │ │ │ │
          │ │ │ │ │ │
          -spec registered() -> [RegName] when RegName :: atom().
          │ │ │ │ │ │
          │ │ │ │ │ │ -

          Returns a list of names that have been registered using register/2.

          For example:

          > registered().
          │ │ │ -[code_server, file_server, init, user, my_db]
          │ │ │ +

          Returns a list of names that have been registered using register/2.

          For example:

          > registered().
          │ │ │ +[code_server, file_server, init, user, my_db]
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ │ │ │ @@ -14491,15 +14491,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

          Returns the process identifier of the calling process.

          For example:

          > self().
          │ │ │ +

          Returns the process identifier of the calling process.

          For example:

          > self().
          │ │ │  <0.26.0>
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ @@ -14741,15 +14741,15 @@ │ │ │

          Returns the process identifier of a new process started by the application of │ │ │ Module:Function to Args.

          error_handler:undefined_function(Module, Function, Args) is │ │ │ evaluated by the new process if Module:Function/Arity does not exist │ │ │ (where Arity is the length of Args). The error handler can be redefined │ │ │ (see process_flag/2). If │ │ │ error_handler is undefined, or the user has redefined the default │ │ │ error_handler and its replacement is undefined, a failure with reason undef │ │ │ -occurs.

          Example:

          > spawn(speed, regulator, [high_speed, thin_cut]).
          │ │ │ +occurs.

          Example:

          > spawn(speed, regulator, [high_speed, thin_cut]).
          │ │ │  <0.13.1>
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ │ │ │ │ │ │

          Raises an exception of class throw. Intended to be used to do non-local │ │ │ returns from functions.

          If evaluated within a catch expression, the │ │ │ -catch expression returns value Any.

          For example:

          > catch throw({hello, there}).
          │ │ │ -        {hello,there}

          If evaluated within a try-block of a │ │ │ +catch expression returns value Any.

          For example:

          > catch throw({hello, there}).
          │ │ │ +        {hello,there}

          If evaluated within a try-block of a │ │ │ try expression, the value Any can be caught │ │ │ within the catch block.

          For example:

          try
          │ │ │ -    throw({my_exception, "Something happened"})
          │ │ │ +    throw({my_exception, "Something happened"})
          │ │ │  catch
          │ │ │ -    throw:{my_exception, Desc} ->
          │ │ │ -        io:format(standard_error, "Error: ~s~n", [Desc])
          │ │ │ +    throw:{my_exception, Desc} ->
          │ │ │ +        io:format(standard_error, "Error: ~s~n", [Desc])
          │ │ │  end

          Failure: nocatch if not caught by an exception handler.

          See the guide about errors and error handling for │ │ │ additional information.

          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │
          │ │ │ @@ -15774,17 +15774,17 @@ │ │ │ trapping exits, an │ │ │ {'EXIT', Id, ExitReason} message due to the link may have been placed in the │ │ │ message queue of the caller before the unlink(Id) call │ │ │ completed. Also note that the {'EXIT', Id, ExitReason} message may be the │ │ │ result of the link, but may also be the result of the unlikee sending the caller │ │ │ an exit signal by calling the exit/2 BIF. Therefore, it may or may not be │ │ │ appropriate to clean up the message queue after a call to │ │ │ -unlink(Id) as follows, when trapping exits:

          unlink(Id),
          │ │ │ +unlink(Id) as follows, when trapping exits:

          unlink(Id),
          │ │ │  receive
          │ │ │ -    {'EXIT', Id, _} ->
          │ │ │ +    {'EXIT', Id, _} ->
          │ │ │          true
          │ │ │  after 0 ->
          │ │ │          true
          │ │ │  end

          The link removal is performed asynchronously. If such a link does not exist, │ │ │ nothing is done. A detailed description of the │ │ │ link protocol can be found in the │ │ │ Distribution Protocol chapter of the ERTS User's Guide.

          Note

          For some important information about distributed signals, see the │ │ │ @@ -15815,15 +15815,15 @@ │ │ │ │ │ │

          -spec unregister(RegName) -> true when RegName :: atom().
          │ │ │ │ │ │
          │ │ │ │ │ │

          Removes the registered name RegName associated with a │ │ │ process identifier or a port identifier from the │ │ │ -name registry.

          For example:

          > unregister(db).
          │ │ │ +name registry.

          For example:

          > unregister(db).
          │ │ │  true

          Keep in mind that you can still receive signals associated with the registered │ │ │ name after it has been unregistered as the sender may have looked up the name │ │ │ before sending to it.

          Users are advised not to unregister system processes.

          Failure: badarg if RegName is not a registered name.

          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ @@ -15849,15 +15849,15 @@ │ │ │
          -spec whereis(RegName) -> pid() | port() | undefined when RegName :: atom().
          │ │ │ │ │ │ │ │ │ │ │ │

          Returns the process identifier or port identifier with the │ │ │ registered name RegName from the │ │ │ name registry. Returns │ │ │ -undefined if the name is not registered.

          For example:

          > whereis(db).
          │ │ │ +undefined if the name is not registered.

          For example:

          > whereis(db).
          │ │ │  <0.43.0>
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ @@ -15924,15 +15924,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

          Equivalent to calling halt(0, []).

          For example:

          > halt().
          │ │ │ +

          Equivalent to calling halt(0, []).

          For example:

          > halt().
          │ │ │  os_prompt%
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ @@ -15955,15 +15955,15 @@ │ │ │ │ │ │
          -spec halt(Status :: non_neg_integer()) -> no_return();
          │ │ │            (Abort :: abort) -> no_return();
          │ │ │            (CrashDumpSlogan :: string()) -> no_return().
          │ │ │ │ │ │
          │ │ │ │ │ │ -

          Equivalent to calling halt(HaltType, []).

          For example:

          > halt(17).
          │ │ │ +

          Equivalent to calling halt(HaltType, []).

          For example:

          > halt(17).
          │ │ │  os_prompt% echo $?
          │ │ │  17
          │ │ │  os_prompt%
          │ │ │
          │ │ │ │ │ │
          │ │ │ │ │ │ @@ -15990,15 +15990,15 @@ │ │ │ │ │ │
          -spec halt(Status :: non_neg_integer(), Options :: halt_options()) -> no_return();
          │ │ │            (Abort :: abort, Options :: halt_options()) -> no_return();
          │ │ │            (CrashDumpSlogan :: string(), Options :: halt_options()) -> no_return().
          │ │ │ │ │ │ │ │ │ │ │ │ -

          Halt the runtime system.

          • halt(Status :: non_neg_integer(), Options :: halt_options())

            Halt the runtime system with status code Status.

            Note

            On many platforms, the OS supports only status codes 0-255. A too large │ │ │ +

            Halt the runtime system.

            • halt(Status :: non_neg_integer(), Options :: halt_options())

              Halt the runtime system with status code Status.

              Note

              On many platforms, the OS supports only status codes 0-255. A too large │ │ │ status code is truncated by clearing the high bits.

              Currently the following options are valid:

              • {flush, EnableFlushing} - If EnableFlushing equals │ │ │ true, which also is the default behavior, the runtime system will perform │ │ │ the following operations before terminating:

                • Flush all outstanding output.
                • Send all Erlang ports exit signals and wait for them to exit.
                • Wait for all async threads to complete all outstanding async jobs.
                • Call all installed NIF on halt callbacks.
                • Wait for all ongoing │ │ │ NIF calls with the delay halt setting enabled │ │ │ to return.
                • Call all installed atexit/on_exit callbacks.

                If EnableFlushing equals false, the runtime system will terminate │ │ │ immediately without performing any of the above listed operations.

                Change

                Runtime systems prior to OTP 26.0 called all installed atexit/on_exit │ │ │ callbacks also when flush was disabled, but as of OTP 26.0 this is no │ │ │ @@ -16007,18 +16007,18 @@ │ │ │ termination of the runtime system. Timeout is in milliseconds. The default │ │ │ value is determined by the the erl +zhft <Timeout> │ │ │ command line flag.

                If flushing has been ongoing for Timeout milliseconds, flushing operations │ │ │ will be interrupted and the runtime system will immediately be terminated │ │ │ with the exit code 255. If flushing is not enabled, the timeout will have │ │ │ no effect on the system.

                See also the erl +zhft <Timeout> command line flag. │ │ │ Note that the shortest timeout set by the command line flag and the │ │ │ -flush_timeout option will be the actual timeout value in effect.

                Since: OTP 27.0

            • halt(Abort :: abort, Options :: halt_options())

              Halt the Erlang runtime system by aborting and produce a core dump if core │ │ │ +flush_timeout option will be the actual timeout value in effect.

              Since: OTP 27.0

          • halt(Abort :: abort, Options :: halt_options())

            Halt the Erlang runtime system by aborting and produce a core dump if core │ │ │ dumping has been enabled in the environment that the runtime system is │ │ │ executing in.

            Note

            The {flush, boolean()} option will be ignored, and │ │ │ -flushing will be disabled.

          • halt(CrashDumpSlogan :: string(), Options :: halt_options())

            Halt the Erlang runtime system and generate an │ │ │ +flushing will be disabled.

        • halt(CrashDumpSlogan :: string(), Options :: halt_options())

          Halt the Erlang runtime system and generate an │ │ │ Erlang crash dump. The string CrashDumpSlogan will be used │ │ │ as slogan in the Erlang crash dump created. The slogan will be trunkated if │ │ │ CrashDumpSlogan is longer than 1023 characters.

          Note

          The {flush, boolean()} option will be ignored, and │ │ │ flushing will be disabled.

          Change

          Behavior changes compared to earlier versions:

          • Before OTP 24.2, the slogan was truncated if CrashDumpSlogan was longer │ │ │ than 200 characters. Now it will be truncated if longer than 1023 │ │ │ characters.
          • Before OTP 20.1, only code points in the range 0-255 were accepted in the │ │ │ slogan. Now any Unicode string is valid.
        │ │ │ @@ -16195,19 +16195,19 @@ │ │ │ (wall_clock) -> {Total_Wallclock_Time, Wallclock_Time_Since_Last_Call} │ │ │ when │ │ │ Total_Wallclock_Time :: non_neg_integer(), │ │ │ Wallclock_Time_Since_Last_Call :: non_neg_integer().
    │ │ │ │ │ │ │ │ │ │ │ │ -

    Returns statistics about the current system.

    The possible flags are:

    • statistics(active_tasks) -> [non_neg_integer()]

      Returns the same as │ │ │ +

      Returns statistics about the current system.

      The possible flags are:

      • statistics(active_tasks) -> [non_neg_integer()]

        Returns the same as │ │ │ statistics(active_tasks_all) with │ │ │ the exception that no information about the dirty IO run queue and its │ │ │ associated schedulers is part of the result. That is, only tasks that are │ │ │ -expected to be CPU bound are part of the result.

        Available since OTP 18.3

      • statistics(active_tasks_all) -> [non_neg_integer()]

        Returns a list where each element represents the amount of active processes and │ │ │ +expected to be CPU bound are part of the result.

        Available since OTP 18.3

      • statistics(active_tasks_all) -> [non_neg_integer()]

        Returns a list where each element represents the amount of active processes and │ │ │ ports on each run queue and its associated schedulers. That is, the number of │ │ │ processes and ports that are ready to run, or are currently running. Values for │ │ │ normal run queues and their associated schedulers are located first in the │ │ │ resulting list. The first element corresponds to scheduler number 1 and so on. │ │ │ If support for dirty schedulers exist, an element with the value for the dirty │ │ │ CPU run queue and its associated dirty CPU schedulers follow and then as last │ │ │ element the value for the dirty IO run queue and its associated dirty IO │ │ │ @@ -16221,44 +16221,44 @@ │ │ │ migrate to other normal run queues. This has to be taken into account when │ │ │ evaluating the result.

        See also │ │ │ statistics(total_active_tasks), │ │ │ statistics(run_queue_lengths), │ │ │ statistics(run_queue_lengths_all), │ │ │ statistics(total_run_queue_lengths), │ │ │ and │ │ │ -statistics(total_run_queue_lengths_all).

        Available since OTP 20.0

      • statistics(context_switches) -> {non_neg_integer(), 0}

        Returns the total number of context switches since the system started.

      • statistics(exact_reductions) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns the number of exact reductions.

        Note

        statistics(exact_reductions) is a more expensive operation │ │ │ -than statistics(reductions).

      • statistics(garbage_collection) ->
        │ │ │ -  { NumerOfGCs :: non_neg_integer(), WordsReclaimed :: non_neg_integer(), 0}

        Returns information about garbage collection, for example:

        > statistics(garbage_collection).
        │ │ │ -{85,23961,0}

        This information can be invalid for some implementations.

      • statistics(io) -> {{input, non_neg_integer()}, {output, non_neg_integer()}}

        Returns Input, which is the total number of bytes received through ports, and │ │ │ -Output, which is the total number of bytes output to ports.

      • statistics(microstate_accounting) -> [MSAcc_Thread]

        Microstate accounting can be used to measure how much time the Erlang runtime │ │ │ +statistics(total_run_queue_lengths_all).

        Available since OTP 20.0

      • statistics(context_switches) -> {non_neg_integer(), 0}

        Returns the total number of context switches since the system started.

      • statistics(exact_reductions) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns the number of exact reductions.

        Note

        statistics(exact_reductions) is a more expensive operation │ │ │ +than statistics(reductions).

      • statistics(garbage_collection) ->
        │ │ │ +  { NumerOfGCs :: non_neg_integer(), WordsReclaimed :: non_neg_integer(), 0}

        Returns information about garbage collection, for example:

        > statistics(garbage_collection).
        │ │ │ +{85,23961,0}

        This information can be invalid for some implementations.

      • statistics(io) -> {{input, non_neg_integer()}, {output, non_neg_integer()}}

        Returns Input, which is the total number of bytes received through ports, and │ │ │ +Output, which is the total number of bytes output to ports.

      • statistics(microstate_accounting) -> [MSAcc_Thread]

        Microstate accounting can be used to measure how much time the Erlang runtime │ │ │ system spends doing various tasks. It is designed to be as lightweight as │ │ │ possible, but some overhead exists when this is enabled. Microstate accounting │ │ │ is meant to be a profiling tool to help finding performance bottlenecks. To │ │ │ start/stop/reset microstate accounting, use system flag │ │ │ microstate_accounting.

        statistics(microstate_accounting) returns a list of maps │ │ │ representing some of the OS threads within ERTS. Each map contains type and │ │ │ id fields that can be used to identify what thread it is, and also a counters │ │ │ field that contains data about how much time has been spent in the various │ │ │ -states.

        Example:

        > erlang:statistics(microstate_accounting).
        │ │ │ -[#{counters => #{aux => 1899182914,
        │ │ │ +states.

        Example:

        > erlang:statistics(microstate_accounting).
        │ │ │ +[#{counters => #{aux => 1899182914,
        │ │ │                   check_io => 2605863602,
        │ │ │                   emulator => 45731880463,
        │ │ │                   gc => 1512206910,
        │ │ │                   other => 5421338456,
        │ │ │                   port => 221631,
        │ │ │ -                 sleep => 5150294100},
        │ │ │ +                 sleep => 5150294100},
        │ │ │     id => 1,
        │ │ │ -   type => scheduler}|...]

        The time unit is the same as returned by os:perf_counter/0. So, to convert it │ │ │ -to milliseconds, you can do something like this:

        lists:map(
        │ │ │ -  fun(#{ counters := Cnt } = M) ->
        │ │ │ -         MsCnt = maps:map(fun(_K, PerfCount) ->
        │ │ │ -                                    erlang:convert_time_unit(PerfCount, perf_counter, 1000)
        │ │ │ -                           end, Cnt),
        │ │ │ -         M#{ counters := MsCnt }
        │ │ │ -  end, erlang:statistics(microstate_accounting)).

        Notice that these values are not guaranteed to be the exact time spent in each │ │ │ + type => scheduler}|...]

        The time unit is the same as returned by os:perf_counter/0. So, to convert it │ │ │ +to milliseconds, you can do something like this:

        lists:map(
        │ │ │ +  fun(#{ counters := Cnt } = M) ->
        │ │ │ +         MsCnt = maps:map(fun(_K, PerfCount) ->
        │ │ │ +                                    erlang:convert_time_unit(PerfCount, perf_counter, 1000)
        │ │ │ +                           end, Cnt),
        │ │ │ +         M#{ counters := MsCnt }
        │ │ │ +  end, erlang:statistics(microstate_accounting)).

        Notice that these values are not guaranteed to be the exact time spent in each │ │ │ state. This is because of various optimisation done to keep the overhead as │ │ │ small as possible.

        MSAcc_Thread_Types:

        • scheduler - The main execution threads that do most of the work. See │ │ │ erl +S for more details.

        • dirty_cpu_scheduler - The threads for long running cpu intensive work. │ │ │ See erl +SDcpu for more details.

        • dirty_io_scheduler - The threads for long running I/O work. See │ │ │ erl +SDio for more details.

        • async - Async threads are used by various linked-in drivers (mainly the │ │ │ file drivers) do offload non-CPU intensive work. See │ │ │ erl +A for more details.

        • aux - Takes care of any work that is not specifically assigned to a │ │ │ @@ -16282,28 +16282,28 @@ │ │ │ states this time is part of the gc state.

        • nif - Time spent in NIFs. Without extra states this time is part of the │ │ │ emulator state.

        • send - Time spent sending messages (processes only). Without extra │ │ │ states this time is part of the emulator state.

        • timers - Time spent managing timers. Without extra states this time is │ │ │ part of the other state.

        The utility module msacc can be used to more easily analyse these │ │ │ statistics.

        Returns undefined if system flag │ │ │ microstate_accounting is │ │ │ turned off.

        The list of thread information is unsorted and can appear in different order │ │ │ -between calls.

        Note

        The threads and states are subject to change without any prior notice.

        Available since OTP 19.0

      • statistics(reductions) -> {Reductions :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about reductions, for example:

        > statistics(reductions).
        │ │ │ -{2046,11}

        Change

        As from ERTS 5.5 (Erlang/OTP R11B), this value does not include reductions │ │ │ +between calls.

        Note

        The threads and states are subject to change without any prior notice.

        Available since OTP 19.0

      • statistics(reductions) -> {Reductions :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about reductions, for example:

        > statistics(reductions).
        │ │ │ +{2046,11}

        Change

        As from ERTS 5.5 (Erlang/OTP R11B), this value does not include reductions │ │ │ performed in current time slices of currently scheduled processes. If an exact │ │ │ value is wanted, use │ │ │ -statistics(exact_reductions).

      • statistics(run_queue) -> non_neg_integer()

        Returns the total length of all normal and dirty CPU run queues. That is, queued │ │ │ +statistics(exact_reductions).

      • statistics(run_queue) -> non_neg_integer()

        Returns the total length of all normal and dirty CPU run queues. That is, queued │ │ │ work that is expected to be CPU bound. The information is gathered atomically. │ │ │ That is, the result is a consistent snapshot of the state, but this operation is │ │ │ much more expensive compared to │ │ │ statistics(total_run_queue_lengths), │ │ │ -especially when a large amount of schedulers is used.

      • statistics(run_queue_lengths) -> [non_neg_integer()]

        Returns the same as │ │ │ +especially when a large amount of schedulers is used.

      • statistics(run_queue_lengths) -> [non_neg_integer()]

        Returns the same as │ │ │ statistics(run_queue_lengths_all) │ │ │ with the exception that no information about the dirty IO run queue is part of │ │ │ the result. That is, only run queues with work that is expected to be CPU bound │ │ │ -is part of the result.

        Available since OTP 18.3

      • statistics(run_queue_lengths_all) -> [non_neg_integer()]

        Returns a list where each element represents the amount of processes and ports │ │ │ +is part of the result.

        Available since OTP 18.3

      • statistics(run_queue_lengths_all) -> [non_neg_integer()]

        Returns a list where each element represents the amount of processes and ports │ │ │ ready to run for each run queue. Values for normal run queues are located first │ │ │ in the resulting list. The first element corresponds to the normal run queue of │ │ │ scheduler number 1 and so on. If support for dirty schedulers exist, values for │ │ │ the dirty CPU run queue and the dirty IO run queue follow (in that order) at the │ │ │ end. The information is not gathered atomically. That is, the result is not │ │ │ necessarily a consistent snapshot of the state, but instead quite efficiently │ │ │ gathered.

        Note

        Each normal scheduler has one run queue that it manages. If dirty schedulers │ │ │ @@ -16315,21 +16315,21 @@ │ │ │ evaluating the result.

        See also │ │ │ statistics(run_queue_lengths), │ │ │ statistics(total_run_queue_lengths_all), │ │ │ statistics(total_run_queue_lengths), │ │ │ statistics(active_tasks), │ │ │ statistics(active_tasks_all), and │ │ │ statistics(total_active_tasks), │ │ │ -statistics(total_active_tasks_all).

        Available since OTP 20.0

      • statistics(runtime) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about runtime, in milliseconds.

        This is the sum of the runtime for all threads in the Erlang runtime system and │ │ │ +statistics(total_active_tasks_all).

        Available since OTP 20.0

      • statistics(runtime) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about runtime, in milliseconds.

        This is the sum of the runtime for all threads in the Erlang runtime system and │ │ │ can therefore be greater than the wall clock time.

        Warning

        This value might wrap due to limitations in the underlying functionality │ │ │ -provided by the operating system that is used.

        Example:

        > statistics(runtime).
        │ │ │ -{1690,1620}
      • statistics(scheduler_wall_time) ->
        │ │ │ -  [{Id :: pos_integer,
        │ │ │ -    ActiveTime :: non_neg_integer(),
        │ │ │ -    TotalTime :: non_neg_integer()}] |
        │ │ │ +provided by the operating system that is used.

        Example:

        > statistics(runtime).
        │ │ │ +{1690,1620}
      • statistics(scheduler_wall_time) ->
        │ │ │ +  [{Id :: pos_integer,
        │ │ │ +    ActiveTime :: non_neg_integer(),
        │ │ │ +    TotalTime :: non_neg_integer()}] |
        │ │ │    undefined

        Returns information describing how much time │ │ │ normal and │ │ │ dirty CPU schedulers in the │ │ │ system have been busy. This value is normally a better indicator of how much │ │ │ load an Erlang node is under instead of looking at the CPU utilization provided │ │ │ by tools such as top or sysstat. This is because scheduler_wall_time also │ │ │ includes time where the scheduler is waiting for some other reasource (such as │ │ │ @@ -16361,60 +16361,60 @@ │ │ │ Dirty CPU schedulers will have scheduler identifiers in the range │ │ │ erlang:system_info(schedulers) < SchedulerId =< erlang:system_info(schedulers) +erlang:system_info(dirty_cpu_schedulers).

        Note

        The different types of schedulers handle specific types of jobs. Every job is │ │ │ assigned to a specific scheduler type. Jobs can migrate between different │ │ │ schedulers of the same type, but never between schedulers of different types. │ │ │ This fact has to be taken under consideration when evaluating the result │ │ │ returned.

        You can use scheduler_wall_time to calculate scheduler utilization. First you │ │ │ take a sample of the values returned by │ │ │ -erlang:statistics(scheduler_wall_time).

        > erlang:system_flag(scheduler_wall_time, true).
        │ │ │ +erlang:statistics(scheduler_wall_time).

        > erlang:system_flag(scheduler_wall_time, true).
        │ │ │  false
        │ │ │ -> Ts0 = lists:sort(erlang:statistics(scheduler_wall_time)), ok.
        │ │ │ +> Ts0 = lists:sort(erlang:statistics(scheduler_wall_time)), ok.
        │ │ │  ok

        Some time later the user takes another snapshot and calculates scheduler │ │ │ -utilization per scheduler, for example:

        > Ts1 = lists:sort(erlang:statistics(scheduler_wall_time)), ok.
        │ │ │ +utilization per scheduler, for example:

        > Ts1 = lists:sort(erlang:statistics(scheduler_wall_time)), ok.
        │ │ │  ok
        │ │ │ -> lists:map(fun({{I, A0, T0}, {I, A1, T1}}) ->
        │ │ │ -        {I, (A1 - A0)/(T1 - T0)} end, lists:zip(Ts0,Ts1)).
        │ │ │ -[{1,0.9743474730177548},
        │ │ │ - {2,0.9744843782751444},
        │ │ │ - {3,0.9995902361669045},
        │ │ │ - {4,0.9738012596572161},
        │ │ │ - {5,0.9717956667018103},
        │ │ │ - {6,0.9739235846420741},
        │ │ │ - {7,0.973237033077876},
        │ │ │ - {8,0.9741297293248656}]

        Using the same snapshots to calculate a total scheduler utilization:

        > {A, T} = lists:foldl(fun({{_, A0, T0}, {_, A1, T1}}, {Ai,Ti}) ->
        │ │ │ -        {Ai + (A1 - A0), Ti + (T1 - T0)} end, {0, 0}, lists:zip(Ts0,Ts1)),
        │ │ │ +> lists:map(fun({{I, A0, T0}, {I, A1, T1}}) ->
        │ │ │ +        {I, (A1 - A0)/(T1 - T0)} end, lists:zip(Ts0,Ts1)).
        │ │ │ +[{1,0.9743474730177548},
        │ │ │ + {2,0.9744843782751444},
        │ │ │ + {3,0.9995902361669045},
        │ │ │ + {4,0.9738012596572161},
        │ │ │ + {5,0.9717956667018103},
        │ │ │ + {6,0.9739235846420741},
        │ │ │ + {7,0.973237033077876},
        │ │ │ + {8,0.9741297293248656}]

        Using the same snapshots to calculate a total scheduler utilization:

        > {A, T} = lists:foldl(fun({{_, A0, T0}, {_, A1, T1}}, {Ai,Ti}) ->
        │ │ │ +        {Ai + (A1 - A0), Ti + (T1 - T0)} end, {0, 0}, lists:zip(Ts0,Ts1)),
        │ │ │    TotalSchedulerUtilization = A/T.
        │ │ │  0.9769136803764825

        Total scheduler utilization will equal 1.0 when all schedulers have been │ │ │ active all the time between the two measurements.

        Another (probably more) useful value is to calculate total scheduler utilization │ │ │ -weighted against maximum amount of available CPU time:

        > WeightedSchedulerUtilization = (TotalSchedulerUtilization
        │ │ │ -                                  * (erlang:system_info(schedulers)
        │ │ │ -                                     + erlang:system_info(dirty_cpu_schedulers)))
        │ │ │ -                                 / erlang:system_info(logical_processors_available).
        │ │ │ +weighted against maximum amount of available CPU time:

        > WeightedSchedulerUtilization = (TotalSchedulerUtilization
        │ │ │ +                                  * (erlang:system_info(schedulers)
        │ │ │ +                                     + erlang:system_info(dirty_cpu_schedulers)))
        │ │ │ +                                 / erlang:system_info(logical_processors_available).
        │ │ │  0.9769136803764825

        This weighted scheduler utilization will reach 1.0 when schedulers are active │ │ │ the same amount of time as maximum available CPU time. If more schedulers exist │ │ │ than available logical processors, this value may be greater than 1.0.

        As of ERTS version 9.0, the Erlang runtime system will as default have more │ │ │ schedulers than logical processors. This due to the dirty schedulers.

        Note

        scheduler_wall_time is by default disabled. To enable it, use │ │ │ -erlang:system_flag(scheduler_wall_time, true).

        Available since OTP R15B01

      • statistics(scheduler_wall_time_all) ->
        │ │ │ -  [{Id :: pos_integer,
        │ │ │ -    ActiveTime :: non_neg_integer(),
        │ │ │ -    TotalTime :: non_neg_integer()}] |
        │ │ │ +erlang:system_flag(scheduler_wall_time, true).

        Available since OTP R15B01

      • statistics(scheduler_wall_time_all) ->
        │ │ │ +  [{Id :: pos_integer,
        │ │ │ +    ActiveTime :: non_neg_integer(),
        │ │ │ +    TotalTime :: non_neg_integer()}] |
        │ │ │    undefined

        Equivalent to │ │ │ statistics(scheduler_wall_time), │ │ │ except that it also include information about all dirty I/O schedulers.

        Dirty IO schedulers will have scheduler identifiers in the range │ │ │ erlang:system_info(schedulers)+erlang:system_info(dirty_cpu_schedulers)< SchedulerId =< erlang:system_info(schedulers) + erlang:system_info(dirty_cpu_schedulers) +erlang:system_info(dirty_io_schedulers).

        Note

        Note that work executing on dirty I/O schedulers are expected to mainly wait │ │ │ for I/O. That is, when you get high scheduler utilization on dirty I/O │ │ │ -schedulers, CPU utilization is not expected to be high due to this work.

        Available since OTP 20.0

      • statistics(total_active_tasks) -> non_neg_integer()

        Equivalent to calling │ │ │ +schedulers, CPU utilization is not expected to be high due to this work.

        Available since OTP 20.0

      • statistics(total_active_tasks) -> non_neg_integer()

        Equivalent to calling │ │ │ lists:sum(statistics(active_tasks)), │ │ │ -but more efficient.

        Available since OTP 18.3

      • statistics(total_active_tasks_all) -> non_neg_integer()

        Equivalent to calling │ │ │ +but more efficient.

        Available since OTP 18.3

      • statistics(total_active_tasks_all) -> non_neg_integer()

        Equivalent to calling │ │ │ lists:sum(statistics(active_tasks_all)), │ │ │ -but more efficient.

        Available since OTP 20.0

      • statistics(total_run_queue_lengths) -> non_neg_integer()

        Equivalent to calling │ │ │ +but more efficient.

        Available since OTP 20.0

      • statistics(total_run_queue_lengths) -> non_neg_integer()

        Equivalent to calling │ │ │ lists:sum(statistics(run_queue_lengths)), │ │ │ -but more efficient.

        Available since OTP 18.3

      • statistics(total_run_queue_lengths_all) -> non_neg_integer()

        Equivalent to calling │ │ │ +but more efficient.

        Available since OTP 18.3

      • statistics(total_run_queue_lengths_all) -> non_neg_integer()

        Equivalent to calling │ │ │ lists:sum(statistics(run_queue_lengths_all)), │ │ │ -but more efficient.

        Available since OTP 20.0

      • statistics(wall_clock) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about wall clock. wall_clock can be used in the same │ │ │ +but more efficient.

        Available since OTP 20.0

      • statistics(wall_clock) -> {Total :: non_neg_integer(), SinceLastCall :: non_neg_integer()}

        Returns information about wall clock. wall_clock can be used in the same │ │ │ manner as runtime, except that real time is measured as opposed to runtime or │ │ │ CPU time.

      │ │ │ │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ @@ -16478,65 +16478,65 @@ │ │ │ when │ │ │ Tracer :: pid() | port() | {module(), term()} | false, │ │ │ PrevTracer :: pid() | port() | {module(), term()} | false; │ │ │ (reset_seq_trace, true) -> true.
    │ │ │ │ │ │ │ │ │ │ │ │ -

    Sets a system flag to the given value.

    The possible flags to set are:

    • system_flag(backtrace_depths, non_neg_integer()) -> non_neg_integer()

      Sets the maximum depth of call stack back-traces in the exit reason element of │ │ │ +

      Sets a system flag to the given value.

      The possible flags to set are:

      • system_flag(backtrace_depths, non_neg_integer()) -> non_neg_integer()

        Sets the maximum depth of call stack back-traces in the exit reason element of │ │ │ 'EXIT' tuples. The flag also limits the stacktrace depth returned by │ │ │ -process_info/2 item current_stacktrace.

        Returns the old value of the flag.

      • system_flag(cpu_topology, cpu_topology()) -> cpu_topology()

        Warning

        This argument is deprecated. Instead of using this argument, use │ │ │ +process_info/2 item current_stacktrace.

        Returns the old value of the flag.

      • system_flag(cpu_topology, cpu_topology()) -> cpu_topology()

        Warning

        This argument is deprecated. Instead of using this argument, use │ │ │ command-line argument +sct in erl.

        When this argument is removed, a final CPU topology to use is determined at │ │ │ emulator boot time.

        Sets the user-defined CpuTopology. The user-defined CPU topology overrides any │ │ │ automatically detected CPU topology. By passing undefined as CpuTopology, │ │ │ the system reverts to the CPU topology automatically detected. The returned │ │ │ value equals the value returned from erlang:system_info(cpu_topology) before │ │ │ the change was made.

        Returns the old value of the flag.

        The CPU topology is used when binding schedulers to logical processors. If │ │ │ schedulers are already bound when the CPU topology is changed, the schedulers │ │ │ are sent a request to rebind according to the new CPU topology.

        The user-defined CPU topology can also be set by passing command-line argument │ │ │ +sct to erl.

        For information on type CpuTopology and more, see │ │ │ erlang:system_info(cpu_topology) as │ │ │ well as command-line flags +sct and │ │ │ -+sbt in erl.

      • system_flag(dirty_cpu_schedulers_online, pos_integer()) -> pos_integer()

        Sets the number of dirty CPU schedulers online. Range is │ │ │ ++sbt in erl.

      • system_flag(dirty_cpu_schedulers_online, pos_integer()) -> pos_integer()

        Sets the number of dirty CPU schedulers online. Range is │ │ │ 1 <= DirtyCPUSchedulersOnline <= N, where N is the smallest of the return │ │ │ values of erlang:system_info(dirty_cpu_schedulers) and │ │ │ erlang:system_info(schedulers_online).

        Returns the old value TRUNCATED DUE TO SIZE LIMIT: 10485760 bytes