--- /srv/rebuilderd/tmp/rebuilderdPzxsKB/inputs/erlang-doc_28.3.1+dfsg-1_all.deb +++ /srv/rebuilderd/tmp/rebuilderdPzxsKB/out/erlang-doc_28.3.1+dfsg-1_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2026-01-15 12:51:58.000000 debian-binary │ --rw-r--r-- 0 0 0 52992 2026-01-15 12:51:58.000000 control.tar.xz │ --rw-r--r-- 0 0 0 19420792 2026-01-15 12:51:58.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 52912 2026-01-15 12:51:58.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 19419648 2026-01-15 12:51:58.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -1141,15 +1141,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/search_data-7F18D215.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/search_data-08729196.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/sidebar_items-93514EDF.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/etop.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/etop_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/introduction_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer.epub │ │ │ │ @@ -1178,15 +1178,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/search_data-4AF5D821.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/search_data-CDB327B2.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/sidebar_items-6B6C5447.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/error_handling.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/getting_started.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/odbc.epub │ │ │ │ @@ -2092,15 +2092,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/search_data-384980D7.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/search_data-97692190.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/sidebar_items-D96EBDFB.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl_eventp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl_examples.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -140,15 +140,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 285 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 288 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 288 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 296 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2373 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5642 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 764565 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 764531 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53677 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 97457 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -361,15 +361,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1060 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6004 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6684 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/api-reference.html │ │ │ --rw-r--r-- 0 root (0) root (0) 97606 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 97570 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 141098 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1_getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9322 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1_introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7448 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1_overview.html │ │ │ -rw-r--r-- 0 root (0) root (0) 79231 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1_spec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35365 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/asn1ct.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1340 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.4.2/doc/html/assets/exclusive_Win_But.gif │ │ │ @@ -407,15 +407,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 10672 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 4963 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/assets/config.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 10726 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/assets/html_logs.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 9561 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/assets/tc_execution.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 21795 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/basics_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 400272 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/common_test.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 400256 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/common_test.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7502 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/common_test_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59626 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/config_file_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25541 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/cover_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 181422 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/ct.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12274 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/ct_cover.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29928 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/ct_ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 76707 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.29.1/doc/html/ct_hooks.html │ │ │ @@ -510,15 +510,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 978 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 36793 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/algorithm_details.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6662 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 133040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/crypto.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 133044 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/crypto.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 326026 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/crypto.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10043 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/crypto_app.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.8/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -555,15 +555,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 21770 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/cond_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 13532 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/function_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 28924 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/interpret.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 14414 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/line_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 40742 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/monitor.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 34504 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/assets/view.jpg │ │ │ --rw-r--r-- 0 root (0) root (0) 221277 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/debugger.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 221278 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/debugger.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13083 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/debugger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52022 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/debugger_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.0.3/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -592,15 +592,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 921 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6786 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 68058 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dialyzer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 68054 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dialyzer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53566 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dialyzer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25892 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dialyzer_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.4/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -627,15 +627,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1143 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8206 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 147392 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 147387 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 253460 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57499 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28976 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_codec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34940 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_dict.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6772 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9512 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23266 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.6/doc/html/diameter_make.html │ │ │ @@ -761,15 +761,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 26294 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/search_data-217400F6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 6139 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/dist/sidebar_items-C4F9BDCA.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33463 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/eldap.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33466 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/eldap.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 94399 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/eldap.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 27891 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5929 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.16/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/ │ │ │ @@ -798,15 +798,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 197213 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/dist/search_data-343A6246.js │ │ │ -rw-r--r-- 0 root (0) root (0) 16112 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/dist/sidebar_items-8FA1B01D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 73724 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/ei.html │ │ │ -rw-r--r-- 0 root (0) root (0) 72775 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/ei_connect.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11735 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/ei_global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26970 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/ei_users_guide.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22660 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/erl_call_cmd.html │ │ │ --rw-r--r-- 0 root (0) root (0) 86458 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/erl_interface.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 86456 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/erl_interface.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 272 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 116186 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5565 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.6.2/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1332 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/.build │ │ │ @@ -841,15 +841,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 81461 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/search_data-CD972D2E.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9281 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/dist/sidebar_items-8B24DAD4.js │ │ │ --rw-r--r-- 0 root (0) root (0) 302591 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 302587 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 22753 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et.html │ │ │ -rw-r--r-- 0 root (0) root (0) 56805 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_collector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52816 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_desc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100666 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9908 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20257 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_selector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45778 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.2/doc/html/et_tutorial.html │ │ │ @@ -882,15 +882,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 80788 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/search_data-12F6E2D9.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3131 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/dist/sidebar_items-5BE25D43.js │ │ │ --rw-r--r-- 0 root (0) root (0) 47209 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/eunit.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 47204 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/eunit.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13563 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/eunit.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6638 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/eunit_surefire.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45385 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5929 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.10.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/ │ │ │ @@ -916,15 +916,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 30038 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/search_data-90441AD8.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5310 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/dist/sidebar_items-E1CCA645.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33769 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/ftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33771 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/ftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 81701 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12856 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/ftp_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7162 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23260 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.4/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/ │ │ │ @@ -960,15 +960,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11377 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/http_uri.html │ │ │ -rw-r--r-- 0 root (0) root (0) 90477 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/httpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 117305 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/httpd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12076 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/httpd_custom_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13393 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/httpd_socket.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44888 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/httpd_util.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 154751 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/inets.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 154748 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/inets.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 25609 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/inets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8647 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/inets_services.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7454 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21176 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/mod_alias.html │ │ │ -rw-r--r-- 0 root (0) root (0) 82372 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/mod_auth.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21844 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/mod_esi.html │ │ │ -rw-r--r-- 0 root (0) root (0) 36914 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.5/doc/html/mod_security.html │ │ │ @@ -1148,15 +1148,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57052 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37162 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/global_group.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24939 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/heart.html │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 185468 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/inet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 92964 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/inet_res.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7721 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/introduction_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 814377 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/kernel.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 814375 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/kernel.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 43555 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/kernel_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 188579 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110979 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70805 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger_cookbook.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15633 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger_disk_log_h.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25604 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger_filters.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34063 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.5/doc/html/logger_formatter.html │ │ │ @@ -1200,15 +1200,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 210302 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/search_data-95FAE7AF.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33463 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/dist/sidebar_items-E82EB9E0.js │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 145572 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 145576 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 198739 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17702 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_architecture.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9842 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_codec_meas.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26200 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_codec_mstone1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9839 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_codec_mstone2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10089 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_codec_transform.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18676 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.8.2/doc/html/megaco_debug.html │ │ │ @@ -1255,15 +1255,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 381432 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/search_data-99081283.js │ │ │ -rw-r--r-- 0 root (0) root (0) 24818 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/dist/sidebar_items-D10D32DD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 223933 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 223934 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 320700 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45468 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_app_a.html │ │ │ -rw-r--r-- 0 root (0) root (0) 87795 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_app_b.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46060 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_app_c.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9869 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_chap1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109248 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_chap2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51392 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.25.1/doc/html/mnesia_chap3.html │ │ │ @@ -1303,22 +1303,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 147948 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/search_data-7F18D215.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 147948 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/search_data-08729196.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12865 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/dist/sidebar_items-93514EDF.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17930 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/etop.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15748 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/etop_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7356 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/introduction_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 73263 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 117518 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 117525 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13889 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7244 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23450 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/observer_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5947 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111535 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/ttb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 165966 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.18.1/doc/html/ttb_ug.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/ │ │ │ @@ -1345,15 +1345,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 78112 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/search_data-4AF5D821.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 78112 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/search_data-CDB327B2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7446 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/dist/sidebar_items-6B6C5447.js │ │ │ -rw-r--r-- 0 root (0) root (0) 13859 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/error_handling.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51373 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8466 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59221 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67870 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.16/doc/html/odbc.epub │ │ │ @@ -1424,15 +1424,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 57977 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/dist/search_data-27E9EF3F.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5729 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/dist/sidebar_items-54E776FB.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 55578 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/leex.html │ │ │ -rw-r--r-- 0 root (0) root (0) 43008 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 45938 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 45941 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/parsetools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 5950 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67786 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.7/doc/html/yecc.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6043 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/404.html │ │ │ @@ -1457,15 +1457,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 154251 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/dist/search_data-509439AA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17511 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/dist/sidebar_items-EAC0C99A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 269 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100206 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 104326 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/public_key.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 104318 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/public_key.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 214566 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/public_key.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10619 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/public_key_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 74419 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/public_key_records.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5959 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 131325 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.20.1/doc/html/using_public_key.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/ │ │ │ @@ -1493,15 +1493,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 90973 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/dist/search_data-9DD4EAF2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 8888 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/dist/sidebar_items-41969784.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47048 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 63130 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 63124 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 100518 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool.html │ │ │ -rw-r--r-- 0 root (0) root (0) 200434 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23138 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/reltool_usage.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.2/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/ │ │ │ @@ -1536,15 +1536,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 9768 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/dtrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47608 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/dyntrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 269 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50748 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/instrument.html │ │ │ -rw-r--r-- 0 root (0) root (0) 64733 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/lttng.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49986 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/msacc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 85798 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 134920 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/runtime_tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 134928 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/runtime_tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7578 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/runtime_tools_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29007 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/scheduler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5968 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12883 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/system_information.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10216 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.3/doc/html/systemtap.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.3/doc/ │ │ │ @@ -1636,15 +1636,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 555077 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/dist/search_data-3E885201.js │ │ │ -rw-r--r-- 0 root (0) root (0) 90019 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/dist/sidebar_items-3635EA93.js │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 69344 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 449156 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 449181 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 147475 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39977 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_advanced_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62871 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_agent_config_files.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51335 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_agent_funct_descr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18113 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_agent_netif.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66591 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8603 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20/doc/html/snmp_app_a.html │ │ │ @@ -1728,15 +1728,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 383678 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/dist/search_data-CD88D1F7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 47445 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/dist/sidebar_items-FB1A7AE1.js │ │ │ -rw-r--r-- 0 root (0) root (0) 23913 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/hardening.html │ │ │ -rw-r--r-- 0 root (0) root (0) 259 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14191 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 249817 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 279446 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 279443 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 257431 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24745 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25441 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44136 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_client_channel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23193 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_client_key_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78519 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_connection.html │ │ │ -rw-r--r-- 0 root (0) root (0) 48819 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.4/doc/html/ssh_file.html │ │ │ @@ -1773,15 +1773,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 499167 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/dist/search_data-3A215FBA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 27981 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/dist/sidebar_items-4629A0AD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 270747 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 215925 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 215915 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 323500 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17341 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12864 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_crl_cache.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21728 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_crl_cache_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_distribution.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14204 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25835 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.1/doc/html/ssl_session_cache_api.html │ │ │ @@ -1883,15 +1883,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110480 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/sets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110236 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/shell.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10133 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/shell_default.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49862 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/shell_docs.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33064 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/slave.html │ │ │ -rw-r--r-- 0 root (0) root (0) 503061 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/sofs.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1570932 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/stdlib.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1570889 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/stdlib.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 16124 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/stdlib_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 191888 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/string.html │ │ │ -rw-r--r-- 0 root (0) root (0) 96239 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/supervisor.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20517 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/supervisor_bridge.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106837 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/sys.html │ │ │ -rw-r--r-- 0 root (0) root (0) 30984 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/terminal_interface.html │ │ │ -rw-r--r-- 0 root (0) root (0) 80982 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-7.2/doc/html/timer.html │ │ │ @@ -1973,15 +1973,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 22906 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/dist/search_data-4B8A9CF1.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3157 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/dist/sidebar_items-EEB857AE.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9543 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7929 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15522 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5920 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 29015 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/tftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 29014 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/tftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 44995 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/tftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11729 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.3/doc/html/tftp_logger.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1139 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/404.html │ │ │ @@ -2022,15 +2022,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67162 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/lcnt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 53463 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/lcnt_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18390 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110202 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28519 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 239727 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 239731 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 173630 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/tprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 183780 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/xref.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39637 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.3/doc/html/xref_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1610 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/.build.gz │ │ │ @@ -2060,15 +2060,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1669227 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/dist/search_data-485B006D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 578833 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/dist/sidebar_items-F11AD736.js │ │ │ -rw-r--r-- 0 root (0) root (0) 1720071 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/gl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77256 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/glu.html │ │ │ -rw-r--r-- 0 root (0) root (0) 260 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 64895 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1608755 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wx.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1608756 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wx.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53718 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wx.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19407 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxAcceleratorEntry.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15122 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxAcceleratorTable.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12365 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxActivateEvent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19088 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxArtProvider.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17610 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxAuiDockArt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62915 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.5.3/doc/html/wxAuiManager.html │ │ │ @@ -2334,15 +2334,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 139595 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/search_data-384980D7.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 139595 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/search_data-97692190.js │ │ │ -rw-r--r-- 0 root (0) root (0) 14846 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/dist/sidebar_items-D96EBDFB.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 93470 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5817 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45005 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24358 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl_eventp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 40749 2026-01-15 12:51:58.000000 ./usr/share/doc/erlang-doc/html/lib/xmerl-2.1.8/doc/html/xmerl_examples.html │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ ├── zipinfo {} │ │ │ │ @@ -1,98 +1,98 @@ │ │ │ │ -Zip file size: 764565 bytes, number of entries: 96 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 26-Jan-15 13:53 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 14641 bx defN 26-Jan-15 13:53 OEBPS/vulnerabilities.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 17920 bx defN 26-Jan-15 13:53 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4671 bx defN 26-Jan-15 13:53 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 54449 bx defN 26-Jan-15 13:53 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2164 bx defN 26-Jan-15 13:53 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 760 bx defN 26-Jan-15 13:53 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46254 bx defN 26-Jan-15 13:53 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12432 bx defN 26-Jan-15 13:53 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7483 bx defN 26-Jan-15 13:53 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63474 bx defN 26-Jan-15 13:53 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 254031 bx defN 26-Jan-15 13:53 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111722 bx defN 26-Jan-15 13:53 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 251367 bx defN 26-Jan-15 13:53 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15503 bx defN 26-Jan-15 13:53 OEBPS/sbom.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70941 bx defN 26-Jan-15 13:53 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20875 bx defN 26-Jan-15 13:53 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 59978 bx defN 26-Jan-15 13:53 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4594 bx defN 26-Jan-15 13:53 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19453 bx defN 26-Jan-15 13:53 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 55169 bx defN 26-Jan-15 13:53 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14475 bx defN 26-Jan-15 13:53 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49540 bx defN 26-Jan-15 13:53 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2355 bx defN 26-Jan-15 13:53 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 783 bx defN 26-Jan-15 13:53 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40214 bx defN 26-Jan-15 13:53 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15199 bx defN 26-Jan-15 13:53 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8499 bx defN 26-Jan-15 13:53 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3850 bx defN 26-Jan-15 13:53 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13415 bx defN 26-Jan-15 13:53 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8957 bx defN 26-Jan-15 13:53 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 10042 bx defN 26-Jan-15 13:53 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15630 bx defN 26-Jan-15 13:53 OEBPS/nominals.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14146 bx defN 26-Jan-15 13:53 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6775 bx defN 26-Jan-15 13:53 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 25848 bx defN 26-Jan-15 13:53 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7010 bx defN 26-Jan-15 13:53 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5475 bx defN 26-Jan-15 13:53 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53372 bx defN 26-Jan-15 13:53 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39592 bx defN 26-Jan-15 13:53 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31437 bx defN 26-Jan-15 13:53 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 52412 bx defN 26-Jan-15 13:53 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2209 bx defN 26-Jan-15 13:53 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 56009 bx defN 26-Jan-15 13:53 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28227 bx defN 26-Jan-15 13:53 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35713 bx defN 26-Jan-15 13:53 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21437 bx defN 26-Jan-15 13:53 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3220 bx defN 26-Jan-15 13:53 OEBPS/howto_debug.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2351 bx defN 26-Jan-15 13:53 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31339 bx defN 26-Jan-15 13:53 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 119133 bx defN 26-Jan-15 13:53 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8451 bx defN 26-Jan-15 13:53 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 268537 bx defN 26-Jan-15 13:53 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2363 bx defN 26-Jan-15 13:53 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26768 bx defN 26-Jan-15 13:53 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16632 bx defN 26-Jan-15 13:53 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13607 bx defN 26-Jan-15 13:53 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 42614 bx defN 26-Jan-15 13:53 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18218 bx defN 26-Jan-15 13:53 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2083 bx defN 26-Jan-15 13:53 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46681 bx defN 26-Jan-15 13:53 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21207 bx defN 26-Jan-15 13:53 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9336 bx defN 26-Jan-15 13:53 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47559 bx defN 26-Jan-15 13:53 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14890 bx defN 26-Jan-15 13:53 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24320 bx defN 26-Jan-15 13:53 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14562 bx defN 26-Jan-15 13:53 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 26-Jan-15 13:53 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36778 bx defN 26-Jan-15 13:53 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15001 bx defN 26-Jan-15 13:53 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 74139 bx defN 26-Jan-15 13:53 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 115065 bx defN 26-Jan-15 13:53 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13868 bx defN 26-Jan-15 13:53 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 130013 bx defN 26-Jan-15 13:53 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 33367 bx defN 26-Jan-15 13:53 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11933 bx defN 26-Jan-15 13:53 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 801 bx defN 26-Jan-15 13:53 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5148 bx defN 26-Jan-15 13:53 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40797 bx defN 26-Jan-15 13:53 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35600 bx defN 26-Jan-15 13:53 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34817 bx defN 26-Jan-15 13:53 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53325 bx defN 26-Jan-15 13:53 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7604 bx defN 26-Jan-15 13:53 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 88568 bx defN 26-Jan-15 13:53 OEBPS/assets/prio-msg-recv.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Jan-15 13:53 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Jan-15 13:53 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 26-Jan-15 13:53 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 26-Jan-15 13:53 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 26-Jan-15 13:53 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 26-Jan-15 13:53 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 26-Jan-15 13:53 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 26-Jan-15 13:53 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 91725 bx defN 26-Jan-15 13:53 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47927 bx defN 26-Jan-15 13:53 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 26-Jan-15 13:53 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 26-Jan-15 13:53 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -96 files, 3263926 bytes uncompressed, 747761 bytes compressed: 77.1% │ │ │ │ +Zip file size: 764531 bytes, number of entries: 96 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 26-Mar-02 07:59 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 14641 bx defN 26-Mar-02 07:59 OEBPS/vulnerabilities.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 17920 bx defN 26-Mar-02 07:59 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4671 bx defN 26-Mar-02 07:59 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 54449 bx defN 26-Mar-02 07:59 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2164 bx defN 26-Mar-02 07:59 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 760 bx defN 26-Mar-02 07:59 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46254 bx defN 26-Mar-02 07:59 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12432 bx defN 26-Mar-02 07:59 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7483 bx defN 26-Mar-02 07:59 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63474 bx defN 26-Mar-02 07:59 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 254031 bx defN 26-Mar-02 07:59 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111722 bx defN 26-Mar-02 07:59 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 251367 bx defN 26-Mar-02 07:59 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15503 bx defN 26-Mar-02 07:59 OEBPS/sbom.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70941 bx defN 26-Mar-02 07:59 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20875 bx defN 26-Mar-02 07:59 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 59978 bx defN 26-Mar-02 07:59 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4594 bx defN 26-Mar-02 07:59 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19453 bx defN 26-Mar-02 07:59 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 55169 bx defN 26-Mar-02 07:59 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14475 bx defN 26-Mar-02 07:59 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49540 bx defN 26-Mar-02 07:59 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2355 bx defN 26-Mar-02 07:59 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 783 bx defN 26-Mar-02 07:59 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40214 bx defN 26-Mar-02 07:59 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15199 bx defN 26-Mar-02 07:59 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8499 bx defN 26-Mar-02 07:59 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3850 bx defN 26-Mar-02 07:59 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13415 bx defN 26-Mar-02 07:59 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8957 bx defN 26-Mar-02 07:59 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 10042 bx defN 26-Mar-02 07:59 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15630 bx defN 26-Mar-02 07:59 OEBPS/nominals.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14146 bx defN 26-Mar-02 07:59 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6775 bx defN 26-Mar-02 07:59 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 25848 bx defN 26-Mar-02 07:59 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7010 bx defN 26-Mar-02 07:59 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5475 bx defN 26-Mar-02 07:59 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53372 bx defN 26-Mar-02 07:59 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39592 bx defN 26-Mar-02 07:59 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31437 bx defN 26-Mar-02 07:59 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 52412 bx defN 26-Mar-02 07:59 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2209 bx defN 26-Mar-02 07:59 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 56009 bx defN 26-Mar-02 07:59 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28227 bx defN 26-Mar-02 07:59 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35713 bx defN 26-Mar-02 07:59 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21437 bx defN 26-Mar-02 07:59 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3220 bx defN 26-Mar-02 07:59 OEBPS/howto_debug.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2351 bx defN 26-Mar-02 07:59 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31339 bx defN 26-Mar-02 07:59 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 119133 bx defN 26-Mar-02 07:59 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8451 bx defN 26-Mar-02 07:59 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 268537 bx defN 26-Mar-02 07:59 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2363 bx defN 26-Mar-02 07:59 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26768 bx defN 26-Mar-02 07:59 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16632 bx defN 26-Mar-02 07:59 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13607 bx defN 26-Mar-02 07:59 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 42614 bx defN 26-Mar-02 07:59 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18218 bx defN 26-Mar-02 07:59 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2083 bx defN 26-Mar-02 07:59 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46681 bx defN 26-Mar-02 07:59 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21207 bx defN 26-Mar-02 07:59 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9336 bx defN 26-Mar-02 07:59 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47559 bx defN 26-Mar-02 07:59 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14890 bx defN 26-Mar-02 07:59 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24320 bx defN 26-Mar-02 07:59 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14562 bx defN 26-Mar-02 07:59 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 26-Mar-02 07:59 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36778 bx defN 26-Mar-02 07:59 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15001 bx defN 26-Mar-02 07:59 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 74139 bx defN 26-Mar-02 07:59 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 115065 bx defN 26-Mar-02 07:59 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13868 bx defN 26-Mar-02 07:59 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 130013 bx defN 26-Mar-02 07:59 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 33367 bx defN 26-Mar-02 07:59 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11933 bx defN 26-Mar-02 07:59 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 801 bx defN 26-Mar-02 07:59 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5148 bx defN 26-Mar-02 07:59 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40797 bx defN 26-Mar-02 07:59 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35600 bx defN 26-Mar-02 07:59 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34817 bx defN 26-Mar-02 07:59 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53325 bx defN 26-Mar-02 07:59 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7604 bx defN 26-Mar-02 07:59 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 88568 bx defN 26-Mar-02 07:59 OEBPS/assets/prio-msg-recv.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-02 07:59 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-02 07:59 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 26-Mar-02 07:59 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 26-Mar-02 07:59 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 26-Mar-02 07:59 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 26-Mar-02 07:59 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 26-Mar-02 07:59 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 26-Mar-02 07:59 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 91725 bx defN 26-Mar-02 07:59 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47927 bx defN 26-Mar-02 07:59 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 26-Mar-02 07:59 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 26-Mar-02 07:59 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +96 files, 3263926 bytes uncompressed, 747727 bytes compressed: 77.1% │ │ │ ├── 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 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ +0000A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ 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 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -0002F Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ +0002B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0002F Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ 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,6594 +31,6594 @@ │ │ │ │ │ │ │ │ 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 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -00064 CRC 87FBD4CF (2281428175) │ │ │ │ -00068 Compressed Size 00000D27 (3367) │ │ │ │ +00060 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +00064 CRC 41D9871E (1104774942) │ │ │ │ +00068 Compressed Size 00000D29 (3369) │ │ │ │ 0006C Uncompressed Size 00003931 (14641) │ │ │ │ 00070 Filename Length 001B (27) │ │ │ │ 00072 Extra Length 001C (28) │ │ │ │ 00074 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x74: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 0008F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 00091 Length 0009 (9) │ │ │ │ 00093 Flags 03 (3) 'Modification Access' │ │ │ │ -00094 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -00098 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ +00094 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +00098 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ 0009C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 0009E Length 000B (11) │ │ │ │ 000A0 Version 01 (1) │ │ │ │ 000A1 UID Size 04 (4) │ │ │ │ 000A2 UID 00000000 (0) │ │ │ │ 000A6 GID Size 04 (4) │ │ │ │ 000A7 GID 00000000 (0) │ │ │ │ 000AB PAYLOAD │ │ │ │ │ │ │ │ -00DD2 LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ -00DD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -00DD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -00DD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -00DDA Compression Method 0008 (8) 'Deflated' │ │ │ │ -00DDC Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -00DE0 CRC DE70EF53 (3731943251) │ │ │ │ -00DE4 Compressed Size 000015AC (5548) │ │ │ │ -00DE8 Uncompressed Size 00004600 (17920) │ │ │ │ -00DEC Filename Length 0014 (20) │ │ │ │ -00DEE Extra Length 001C (28) │ │ │ │ -00DF0 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xDF0: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -00E04 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -00E06 Length 0009 (9) │ │ │ │ -00E08 Flags 03 (3) 'Modification Access' │ │ │ │ -00E09 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -00E0D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -00E11 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -00E13 Length 000B (11) │ │ │ │ -00E15 Version 01 (1) │ │ │ │ -00E16 UID Size 04 (4) │ │ │ │ -00E17 UID 00000000 (0) │ │ │ │ -00E1B GID Size 04 (4) │ │ │ │ -00E1C GID 00000000 (0) │ │ │ │ -00E20 PAYLOAD │ │ │ │ - │ │ │ │ -023CC LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ -023D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -023D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -023D2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -023D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -023D6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -023DA CRC 4F1D5C6A (1327324266) │ │ │ │ -023DE Compressed Size 000006D3 (1747) │ │ │ │ -023E2 Uncompressed Size 0000123F (4671) │ │ │ │ -023E6 Filename Length 0013 (19) │ │ │ │ -023E8 Extra Length 001C (28) │ │ │ │ -023EA Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x23EA: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -023FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -023FF Length 0009 (9) │ │ │ │ -02401 Flags 03 (3) 'Modification Access' │ │ │ │ -02402 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -02406 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -0240A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0240C Length 000B (11) │ │ │ │ -0240E Version 01 (1) │ │ │ │ -0240F UID Size 04 (4) │ │ │ │ -02410 UID 00000000 (0) │ │ │ │ -02414 GID Size 04 (4) │ │ │ │ -02415 GID 00000000 (0) │ │ │ │ -02419 PAYLOAD │ │ │ │ - │ │ │ │ -02AEC LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -02AF0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -02AF1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -02AF2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -02AF4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -02AF6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -02AFA CRC CF28943C (3475543100) │ │ │ │ -02AFE Compressed Size 00002E66 (11878) │ │ │ │ -02B02 Uncompressed Size 0000D4B1 (54449) │ │ │ │ -02B06 Filename Length 0014 (20) │ │ │ │ -02B08 Extra Length 001C (28) │ │ │ │ -02B0A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B0A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -02B1E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -02B20 Length 0009 (9) │ │ │ │ -02B22 Flags 03 (3) 'Modification Access' │ │ │ │ -02B23 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -02B27 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -02B2B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -02B2D Length 000B (11) │ │ │ │ -02B2F Version 01 (1) │ │ │ │ -02B30 UID Size 04 (4) │ │ │ │ -02B31 UID 00000000 (0) │ │ │ │ -02B35 GID Size 04 (4) │ │ │ │ -02B36 GID 00000000 (0) │ │ │ │ -02B3A PAYLOAD │ │ │ │ - │ │ │ │ -059A0 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ -059A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -059A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -059A6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -059A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -059AA Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -059AE CRC 5467FF9A (1416101786) │ │ │ │ -059B2 Compressed Size 000003ED (1005) │ │ │ │ -059B6 Uncompressed Size 00000874 (2164) │ │ │ │ -059BA Filename Length 0014 (20) │ │ │ │ -059BC Extra Length 001C (28) │ │ │ │ -059BE Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x59BE: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -059D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -059D4 Length 0009 (9) │ │ │ │ -059D6 Flags 03 (3) 'Modification Access' │ │ │ │ -059D7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -059DB Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -059DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -059E1 Length 000B (11) │ │ │ │ -059E3 Version 01 (1) │ │ │ │ -059E4 UID Size 04 (4) │ │ │ │ -059E5 UID 00000000 (0) │ │ │ │ -059E9 GID Size 04 (4) │ │ │ │ -059EA GID 00000000 (0) │ │ │ │ -059EE PAYLOAD │ │ │ │ - │ │ │ │ -05DDB LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ -05DDF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -05DE0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -05DE1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -05DE3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -05DE5 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -05DE9 CRC 58D4C95E (1490340190) │ │ │ │ -05DED Compressed Size 000001AC (428) │ │ │ │ -05DF1 Uncompressed Size 000002F8 (760) │ │ │ │ -05DF5 Filename Length 0011 (17) │ │ │ │ -05DF7 Extra Length 001C (28) │ │ │ │ -05DF9 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5DF9: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -05E0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -05E0C Length 0009 (9) │ │ │ │ -05E0E Flags 03 (3) 'Modification Access' │ │ │ │ -05E0F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -05E13 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -05E17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -05E19 Length 000B (11) │ │ │ │ -05E1B Version 01 (1) │ │ │ │ -05E1C UID Size 04 (4) │ │ │ │ -05E1D UID 00000000 (0) │ │ │ │ -05E21 GID Size 04 (4) │ │ │ │ -05E22 GID 00000000 (0) │ │ │ │ -05E26 PAYLOAD │ │ │ │ - │ │ │ │ -05FD2 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ -05FD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -05FD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -05FD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -05FDA Compression Method 0008 (8) 'Deflated' │ │ │ │ -05FDC Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -05FE0 CRC 1BB0B15A (464564570) │ │ │ │ -05FE4 Compressed Size 000020C5 (8389) │ │ │ │ -05FE8 Uncompressed Size 0000B4AE (46254) │ │ │ │ -05FEC Filename Length 001B (27) │ │ │ │ -05FEE Extra Length 001C (28) │ │ │ │ -05FF0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5FF0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0600B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0600D Length 0009 (9) │ │ │ │ -0600F Flags 03 (3) 'Modification Access' │ │ │ │ -06010 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -06014 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -06018 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0601A Length 000B (11) │ │ │ │ -0601C Version 01 (1) │ │ │ │ -0601D UID Size 04 (4) │ │ │ │ -0601E UID 00000000 (0) │ │ │ │ -06022 GID Size 04 (4) │ │ │ │ -06023 GID 00000000 (0) │ │ │ │ -06027 PAYLOAD │ │ │ │ - │ │ │ │ -080EC LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -080F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -080F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -080F2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -080F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -080F6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -080FA CRC 5EB57D9F (1588952479) │ │ │ │ -080FE Compressed Size 00000E64 (3684) │ │ │ │ -08102 Uncompressed Size 00003090 (12432) │ │ │ │ -08106 Filename Length 001D (29) │ │ │ │ -08108 Extra Length 001C (28) │ │ │ │ -0810A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x810A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08127 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08129 Length 0009 (9) │ │ │ │ -0812B Flags 03 (3) 'Modification Access' │ │ │ │ -0812C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -08130 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -08134 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08136 Length 000B (11) │ │ │ │ -08138 Version 01 (1) │ │ │ │ -08139 UID Size 04 (4) │ │ │ │ -0813A UID 00000000 (0) │ │ │ │ -0813E GID Size 04 (4) │ │ │ │ -0813F GID 00000000 (0) │ │ │ │ -08143 PAYLOAD │ │ │ │ - │ │ │ │ -08FA7 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08FAB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08FAC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08FAD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08FAF Compression Method 0008 (8) 'Deflated' │ │ │ │ -08FB1 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -08FB5 CRC 15AABA29 (363510313) │ │ │ │ -08FB9 Compressed Size 00000991 (2449) │ │ │ │ -08FBD Uncompressed Size 00001D3B (7483) │ │ │ │ -08FC1 Filename Length 0019 (25) │ │ │ │ -08FC3 Extra Length 001C (28) │ │ │ │ -08FC5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8FC5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08FDE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08FE0 Length 0009 (9) │ │ │ │ -08FE2 Flags 03 (3) 'Modification Access' │ │ │ │ -08FE3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -08FE7 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -08FEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08FED Length 000B (11) │ │ │ │ -08FEF Version 01 (1) │ │ │ │ -08FF0 UID Size 04 (4) │ │ │ │ -08FF1 UID 00000000 (0) │ │ │ │ -08FF5 GID Size 04 (4) │ │ │ │ -08FF6 GID 00000000 (0) │ │ │ │ -08FFA PAYLOAD │ │ │ │ - │ │ │ │ -0998B LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0998F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -09990 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -09991 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -09993 Compression Method 0008 (8) 'Deflated' │ │ │ │ -09995 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -09999 CRC B84AAC32 (3091901490) │ │ │ │ -0999D Compressed Size 00003877 (14455) │ │ │ │ -099A1 Uncompressed Size 0000F7F2 (63474) │ │ │ │ -099A5 Filename Length 0015 (21) │ │ │ │ -099A7 Extra Length 001C (28) │ │ │ │ -099A9 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x99A9: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -099BE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -099C0 Length 0009 (9) │ │ │ │ -099C2 Flags 03 (3) 'Modification Access' │ │ │ │ -099C3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -099C7 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -099CB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -099CD Length 000B (11) │ │ │ │ -099CF Version 01 (1) │ │ │ │ -099D0 UID Size 04 (4) │ │ │ │ -099D1 UID 00000000 (0) │ │ │ │ -099D5 GID Size 04 (4) │ │ │ │ -099D6 GID 00000000 (0) │ │ │ │ -099DA PAYLOAD │ │ │ │ - │ │ │ │ -0D251 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -0D255 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0D256 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0D257 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0D259 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0D25B Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -0D25F CRC F93107F8 (4180740088) │ │ │ │ -0D263 Compressed Size 0000AB04 (43780) │ │ │ │ -0D267 Uncompressed Size 0003E04F (254031) │ │ │ │ -0D26B Filename Length 0012 (18) │ │ │ │ -0D26D Extra Length 001C (28) │ │ │ │ -0D26F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xD26F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0D281 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0D283 Length 0009 (9) │ │ │ │ -0D285 Flags 03 (3) 'Modification Access' │ │ │ │ -0D286 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -0D28A Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -0D28E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0D290 Length 000B (11) │ │ │ │ -0D292 Version 01 (1) │ │ │ │ -0D293 UID Size 04 (4) │ │ │ │ -0D294 UID 00000000 (0) │ │ │ │ -0D298 GID Size 04 (4) │ │ │ │ -0D299 GID 00000000 (0) │ │ │ │ -0D29D PAYLOAD │ │ │ │ - │ │ │ │ -17DA1 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ -17DA5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -17DA6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -17DA7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -17DA9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -17DAB Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -17DAF CRC 86F0540D (2263897101) │ │ │ │ -17DB3 Compressed Size 00003B0A (15114) │ │ │ │ -17DB7 Uncompressed Size 0001B46A (111722) │ │ │ │ -17DBB Filename Length 0015 (21) │ │ │ │ -17DBD Extra Length 001C (28) │ │ │ │ -17DBF Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x17DBF: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -17DD4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -17DD6 Length 0009 (9) │ │ │ │ -17DD8 Flags 03 (3) 'Modification Access' │ │ │ │ -17DD9 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -17DDD Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -17DE1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -17DE3 Length 000B (11) │ │ │ │ -17DE5 Version 01 (1) │ │ │ │ -17DE6 UID Size 04 (4) │ │ │ │ -17DE7 UID 00000000 (0) │ │ │ │ -17DEB GID Size 04 (4) │ │ │ │ -17DEC GID 00000000 (0) │ │ │ │ -17DF0 PAYLOAD │ │ │ │ - │ │ │ │ -1B8FA LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -1B8FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -1B8FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -1B900 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -1B902 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1B904 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -1B908 CRC 3EF52A66 (1056254566) │ │ │ │ -1B90C Compressed Size 000091A7 (37287) │ │ │ │ -1B910 Uncompressed Size 0003D5E7 (251367) │ │ │ │ -1B914 Filename Length 0014 (20) │ │ │ │ -1B916 Extra Length 001C (28) │ │ │ │ -1B918 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x1B918: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -1B92C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -1B92E Length 0009 (9) │ │ │ │ -1B930 Flags 03 (3) 'Modification Access' │ │ │ │ -1B931 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -1B935 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -1B939 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -1B93B Length 000B (11) │ │ │ │ -1B93D Version 01 (1) │ │ │ │ -1B93E UID Size 04 (4) │ │ │ │ -1B93F UID 00000000 (0) │ │ │ │ -1B943 GID Size 04 (4) │ │ │ │ -1B944 GID 00000000 (0) │ │ │ │ -1B948 PAYLOAD │ │ │ │ - │ │ │ │ -24AEF LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -24AF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -24AF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -24AF5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -24AF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -24AF9 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -24AFD CRC 3A7A1761 (981079905) │ │ │ │ -24B01 Compressed Size 00001217 (4631) │ │ │ │ -24B05 Uncompressed Size 00003C8F (15503) │ │ │ │ -24B09 Filename Length 0010 (16) │ │ │ │ -24B0B Extra Length 001C (28) │ │ │ │ -24B0D Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x24B0D: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -24B1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -24B1F Length 0009 (9) │ │ │ │ -24B21 Flags 03 (3) 'Modification Access' │ │ │ │ -24B22 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -24B26 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -24B2A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -24B2C Length 000B (11) │ │ │ │ -24B2E Version 01 (1) │ │ │ │ -24B2F UID Size 04 (4) │ │ │ │ -24B30 UID 00000000 (0) │ │ │ │ -24B34 GID Size 04 (4) │ │ │ │ -24B35 GID 00000000 (0) │ │ │ │ -24B39 PAYLOAD │ │ │ │ - │ │ │ │ -25D50 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -25D54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -25D55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -25D56 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -25D58 Compression Method 0008 (8) 'Deflated' │ │ │ │ -25D5A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -25D5E CRC 0B910DE0 (194055648) │ │ │ │ -25D62 Compressed Size 00002A72 (10866) │ │ │ │ -25D66 Uncompressed Size 0001151D (70941) │ │ │ │ -25D6A Filename Length 0016 (22) │ │ │ │ -25D6C Extra Length 001C (28) │ │ │ │ -25D6E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x25D6E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -25D84 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -25D86 Length 0009 (9) │ │ │ │ -25D88 Flags 03 (3) 'Modification Access' │ │ │ │ -25D89 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -25D8D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -25D91 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -25D93 Length 000B (11) │ │ │ │ -25D95 Version 01 (1) │ │ │ │ -25D96 UID Size 04 (4) │ │ │ │ -25D97 UID 00000000 (0) │ │ │ │ -25D9B GID Size 04 (4) │ │ │ │ -25D9C GID 00000000 (0) │ │ │ │ -25DA0 PAYLOAD │ │ │ │ - │ │ │ │ -28812 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -28816 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -28817 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -28818 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2881A Compression Method 0008 (8) 'Deflated' │ │ │ │ -2881C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -28820 CRC 1DBD56C5 (498947781) │ │ │ │ -28824 Compressed Size 000014D7 (5335) │ │ │ │ -28828 Uncompressed Size 0000518B (20875) │ │ │ │ -2882C Filename Length 001D (29) │ │ │ │ -2882E Extra Length 001C (28) │ │ │ │ -28830 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x28830: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2884D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2884F Length 0009 (9) │ │ │ │ -28851 Flags 03 (3) 'Modification Access' │ │ │ │ -28852 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -28856 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2885A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2885C Length 000B (11) │ │ │ │ -2885E Version 01 (1) │ │ │ │ -2885F UID Size 04 (4) │ │ │ │ -28860 UID 00000000 (0) │ │ │ │ -28864 GID Size 04 (4) │ │ │ │ -28865 GID 00000000 (0) │ │ │ │ -28869 PAYLOAD │ │ │ │ +00DD4 LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ +00DD8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +00DD9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +00DDA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +00DDC Compression Method 0008 (8) 'Deflated' │ │ │ │ +00DDE Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +00DE2 CRC DE70EF53 (3731943251) │ │ │ │ +00DE6 Compressed Size 000015AC (5548) │ │ │ │ +00DEA Uncompressed Size 00004600 (17920) │ │ │ │ +00DEE Filename Length 0014 (20) │ │ │ │ +00DF0 Extra Length 001C (28) │ │ │ │ +00DF2 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xDF2: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +00E06 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +00E08 Length 0009 (9) │ │ │ │ +00E0A Flags 03 (3) 'Modification Access' │ │ │ │ +00E0B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +00E0F Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +00E13 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +00E15 Length 000B (11) │ │ │ │ +00E17 Version 01 (1) │ │ │ │ +00E18 UID Size 04 (4) │ │ │ │ +00E19 UID 00000000 (0) │ │ │ │ +00E1D GID Size 04 (4) │ │ │ │ +00E1E GID 00000000 (0) │ │ │ │ +00E22 PAYLOAD │ │ │ │ + │ │ │ │ +023CE LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ +023D2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +023D3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +023D4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +023D6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +023D8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +023DC CRC 4F1D5C6A (1327324266) │ │ │ │ +023E0 Compressed Size 000006D3 (1747) │ │ │ │ +023E4 Uncompressed Size 0000123F (4671) │ │ │ │ +023E8 Filename Length 0013 (19) │ │ │ │ +023EA Extra Length 001C (28) │ │ │ │ +023EC Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x23EC: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +023FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +02401 Length 0009 (9) │ │ │ │ +02403 Flags 03 (3) 'Modification Access' │ │ │ │ +02404 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +02408 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0240C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0240E Length 000B (11) │ │ │ │ +02410 Version 01 (1) │ │ │ │ +02411 UID Size 04 (4) │ │ │ │ +02412 UID 00000000 (0) │ │ │ │ +02416 GID Size 04 (4) │ │ │ │ +02417 GID 00000000 (0) │ │ │ │ +0241B PAYLOAD │ │ │ │ + │ │ │ │ +02AEE LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +02AF2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +02AF3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +02AF4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +02AF6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +02AF8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +02AFC CRC 6D3889B0 (1832421808) │ │ │ │ +02B00 Compressed Size 00002E6C (11884) │ │ │ │ +02B04 Uncompressed Size 0000D4B1 (54449) │ │ │ │ +02B08 Filename Length 0014 (20) │ │ │ │ +02B0A Extra Length 001C (28) │ │ │ │ +02B0C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B0C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +02B20 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +02B22 Length 0009 (9) │ │ │ │ +02B24 Flags 03 (3) 'Modification Access' │ │ │ │ +02B25 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +02B29 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +02B2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +02B2F Length 000B (11) │ │ │ │ +02B31 Version 01 (1) │ │ │ │ +02B32 UID Size 04 (4) │ │ │ │ +02B33 UID 00000000 (0) │ │ │ │ +02B37 GID Size 04 (4) │ │ │ │ +02B38 GID 00000000 (0) │ │ │ │ +02B3C PAYLOAD │ │ │ │ + │ │ │ │ +059A8 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ +059AC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +059AD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +059AE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +059B0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +059B2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +059B6 CRC 5467FF9A (1416101786) │ │ │ │ +059BA Compressed Size 000003ED (1005) │ │ │ │ +059BE Uncompressed Size 00000874 (2164) │ │ │ │ +059C2 Filename Length 0014 (20) │ │ │ │ +059C4 Extra Length 001C (28) │ │ │ │ +059C6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x59C6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +059DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +059DC Length 0009 (9) │ │ │ │ +059DE Flags 03 (3) 'Modification Access' │ │ │ │ +059DF Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +059E3 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +059E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +059E9 Length 000B (11) │ │ │ │ +059EB Version 01 (1) │ │ │ │ +059EC UID Size 04 (4) │ │ │ │ +059ED UID 00000000 (0) │ │ │ │ +059F1 GID Size 04 (4) │ │ │ │ +059F2 GID 00000000 (0) │ │ │ │ +059F6 PAYLOAD │ │ │ │ + │ │ │ │ +05DE3 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ +05DE7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +05DE8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +05DE9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +05DEB Compression Method 0008 (8) 'Deflated' │ │ │ │ +05DED Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +05DF1 CRC 58D4C95E (1490340190) │ │ │ │ +05DF5 Compressed Size 000001AC (428) │ │ │ │ +05DF9 Uncompressed Size 000002F8 (760) │ │ │ │ +05DFD Filename Length 0011 (17) │ │ │ │ +05DFF Extra Length 001C (28) │ │ │ │ +05E01 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5E01: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +05E12 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +05E14 Length 0009 (9) │ │ │ │ +05E16 Flags 03 (3) 'Modification Access' │ │ │ │ +05E17 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +05E1B Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +05E1F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +05E21 Length 000B (11) │ │ │ │ +05E23 Version 01 (1) │ │ │ │ +05E24 UID Size 04 (4) │ │ │ │ +05E25 UID 00000000 (0) │ │ │ │ +05E29 GID Size 04 (4) │ │ │ │ +05E2A GID 00000000 (0) │ │ │ │ +05E2E PAYLOAD │ │ │ │ + │ │ │ │ +05FDA LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ +05FDE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +05FDF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +05FE0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +05FE2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +05FE4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +05FE8 CRC D80B7767 (3624630119) │ │ │ │ +05FEC Compressed Size 000020C8 (8392) │ │ │ │ +05FF0 Uncompressed Size 0000B4AE (46254) │ │ │ │ +05FF4 Filename Length 001B (27) │ │ │ │ +05FF6 Extra Length 001C (28) │ │ │ │ +05FF8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5FF8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +06013 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +06015 Length 0009 (9) │ │ │ │ +06017 Flags 03 (3) 'Modification Access' │ │ │ │ +06018 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0601C Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +06020 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +06022 Length 000B (11) │ │ │ │ +06024 Version 01 (1) │ │ │ │ +06025 UID Size 04 (4) │ │ │ │ +06026 UID 00000000 (0) │ │ │ │ +0602A GID Size 04 (4) │ │ │ │ +0602B GID 00000000 (0) │ │ │ │ +0602F PAYLOAD │ │ │ │ + │ │ │ │ +080F7 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ +080FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +080FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +080FD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +080FF Compression Method 0008 (8) 'Deflated' │ │ │ │ +08101 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +08105 CRC 5EB57D9F (1588952479) │ │ │ │ +08109 Compressed Size 00000E64 (3684) │ │ │ │ +0810D Uncompressed Size 00003090 (12432) │ │ │ │ +08111 Filename Length 001D (29) │ │ │ │ +08113 Extra Length 001C (28) │ │ │ │ +08115 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8115: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08132 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08134 Length 0009 (9) │ │ │ │ +08136 Flags 03 (3) 'Modification Access' │ │ │ │ +08137 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0813B Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0813F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08141 Length 000B (11) │ │ │ │ +08143 Version 01 (1) │ │ │ │ +08144 UID Size 04 (4) │ │ │ │ +08145 UID 00000000 (0) │ │ │ │ +08149 GID Size 04 (4) │ │ │ │ +0814A GID 00000000 (0) │ │ │ │ +0814E PAYLOAD │ │ │ │ + │ │ │ │ +08FB2 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08FB6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08FB7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08FB8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08FBA Compression Method 0008 (8) 'Deflated' │ │ │ │ +08FBC Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +08FC0 CRC 15AABA29 (363510313) │ │ │ │ +08FC4 Compressed Size 00000991 (2449) │ │ │ │ +08FC8 Uncompressed Size 00001D3B (7483) │ │ │ │ +08FCC Filename Length 0019 (25) │ │ │ │ +08FCE Extra Length 001C (28) │ │ │ │ +08FD0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8FD0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08FE9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08FEB Length 0009 (9) │ │ │ │ +08FED Flags 03 (3) 'Modification Access' │ │ │ │ +08FEE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +08FF2 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +08FF6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08FF8 Length 000B (11) │ │ │ │ +08FFA Version 01 (1) │ │ │ │ +08FFB UID Size 04 (4) │ │ │ │ +08FFC UID 00000000 (0) │ │ │ │ +09000 GID Size 04 (4) │ │ │ │ +09001 GID 00000000 (0) │ │ │ │ +09005 PAYLOAD │ │ │ │ + │ │ │ │ +09996 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +0999A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0999B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0999C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0999E Compression Method 0008 (8) 'Deflated' │ │ │ │ +099A0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +099A4 CRC 9F9CDC44 (2677857348) │ │ │ │ +099A8 Compressed Size 00003878 (14456) │ │ │ │ +099AC Uncompressed Size 0000F7F2 (63474) │ │ │ │ +099B0 Filename Length 0015 (21) │ │ │ │ +099B2 Extra Length 001C (28) │ │ │ │ +099B4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x99B4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +099C9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +099CB Length 0009 (9) │ │ │ │ +099CD Flags 03 (3) 'Modification Access' │ │ │ │ +099CE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +099D2 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +099D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +099D8 Length 000B (11) │ │ │ │ +099DA Version 01 (1) │ │ │ │ +099DB UID Size 04 (4) │ │ │ │ +099DC UID 00000000 (0) │ │ │ │ +099E0 GID Size 04 (4) │ │ │ │ +099E1 GID 00000000 (0) │ │ │ │ +099E5 PAYLOAD │ │ │ │ + │ │ │ │ +0D25D LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +0D261 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0D262 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0D263 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0D265 Compression Method 0008 (8) 'Deflated' │ │ │ │ +0D267 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0D26B CRC C3F9818D (3287908749) │ │ │ │ +0D26F Compressed Size 0000AB05 (43781) │ │ │ │ +0D273 Uncompressed Size 0003E04F (254031) │ │ │ │ +0D277 Filename Length 0012 (18) │ │ │ │ +0D279 Extra Length 001C (28) │ │ │ │ +0D27B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xD27B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0D28D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0D28F Length 0009 (9) │ │ │ │ +0D291 Flags 03 (3) 'Modification Access' │ │ │ │ +0D292 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0D296 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +0D29A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0D29C Length 000B (11) │ │ │ │ +0D29E Version 01 (1) │ │ │ │ +0D29F UID Size 04 (4) │ │ │ │ +0D2A0 UID 00000000 (0) │ │ │ │ +0D2A4 GID Size 04 (4) │ │ │ │ +0D2A5 GID 00000000 (0) │ │ │ │ +0D2A9 PAYLOAD │ │ │ │ + │ │ │ │ +17DAE LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ +17DB2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +17DB3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +17DB4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +17DB6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +17DB8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +17DBC CRC 36241BA5 (908336037) │ │ │ │ +17DC0 Compressed Size 00003B0A (15114) │ │ │ │ +17DC4 Uncompressed Size 0001B46A (111722) │ │ │ │ +17DC8 Filename Length 0015 (21) │ │ │ │ +17DCA Extra Length 001C (28) │ │ │ │ +17DCC Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x17DCC: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +17DE1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +17DE3 Length 0009 (9) │ │ │ │ +17DE5 Flags 03 (3) 'Modification Access' │ │ │ │ +17DE6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +17DEA Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +17DEE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +17DF0 Length 000B (11) │ │ │ │ +17DF2 Version 01 (1) │ │ │ │ +17DF3 UID Size 04 (4) │ │ │ │ +17DF4 UID 00000000 (0) │ │ │ │ +17DF8 GID Size 04 (4) │ │ │ │ +17DF9 GID 00000000 (0) │ │ │ │ +17DFD PAYLOAD │ │ │ │ + │ │ │ │ +1B907 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +1B90B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +1B90C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +1B90D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +1B90F Compression Method 0008 (8) 'Deflated' │ │ │ │ +1B911 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +1B915 CRC B962F612 (3110270482) │ │ │ │ +1B919 Compressed Size 00009199 (37273) │ │ │ │ +1B91D Uncompressed Size 0003D5E7 (251367) │ │ │ │ +1B921 Filename Length 0014 (20) │ │ │ │ +1B923 Extra Length 001C (28) │ │ │ │ +1B925 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x1B925: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +1B939 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +1B93B Length 0009 (9) │ │ │ │ +1B93D Flags 03 (3) 'Modification Access' │ │ │ │ +1B93E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +1B942 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +1B946 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +1B948 Length 000B (11) │ │ │ │ +1B94A Version 01 (1) │ │ │ │ +1B94B UID Size 04 (4) │ │ │ │ +1B94C UID 00000000 (0) │ │ │ │ +1B950 GID Size 04 (4) │ │ │ │ +1B951 GID 00000000 (0) │ │ │ │ +1B955 PAYLOAD │ │ │ │ + │ │ │ │ +24AEE LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +24AF2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +24AF3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +24AF4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +24AF6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +24AF8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +24AFC CRC 3A7A1761 (981079905) │ │ │ │ +24B00 Compressed Size 00001217 (4631) │ │ │ │ +24B04 Uncompressed Size 00003C8F (15503) │ │ │ │ +24B08 Filename Length 0010 (16) │ │ │ │ +24B0A Extra Length 001C (28) │ │ │ │ +24B0C Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x24B0C: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +24B1C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +24B1E Length 0009 (9) │ │ │ │ +24B20 Flags 03 (3) 'Modification Access' │ │ │ │ +24B21 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +24B25 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +24B29 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +24B2B Length 000B (11) │ │ │ │ +24B2D Version 01 (1) │ │ │ │ +24B2E UID Size 04 (4) │ │ │ │ +24B2F UID 00000000 (0) │ │ │ │ +24B33 GID Size 04 (4) │ │ │ │ +24B34 GID 00000000 (0) │ │ │ │ +24B38 PAYLOAD │ │ │ │ + │ │ │ │ +25D4F LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +25D53 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +25D54 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +25D55 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +25D57 Compression Method 0008 (8) 'Deflated' │ │ │ │ +25D59 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +25D5D CRC F17726AF (4051117743) │ │ │ │ +25D61 Compressed Size 00002A72 (10866) │ │ │ │ +25D65 Uncompressed Size 0001151D (70941) │ │ │ │ +25D69 Filename Length 0016 (22) │ │ │ │ +25D6B Extra Length 001C (28) │ │ │ │ +25D6D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x25D6D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +25D83 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +25D85 Length 0009 (9) │ │ │ │ +25D87 Flags 03 (3) 'Modification Access' │ │ │ │ +25D88 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +25D8C Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +25D90 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +25D92 Length 000B (11) │ │ │ │ +25D94 Version 01 (1) │ │ │ │ +25D95 UID Size 04 (4) │ │ │ │ +25D96 UID 00000000 (0) │ │ │ │ +25D9A GID Size 04 (4) │ │ │ │ +25D9B GID 00000000 (0) │ │ │ │ +25D9F PAYLOAD │ │ │ │ + │ │ │ │ +28811 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +28815 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +28816 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +28817 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +28819 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2881B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2881F CRC 89A3D790 (2309216144) │ │ │ │ +28823 Compressed Size 000014D8 (5336) │ │ │ │ +28827 Uncompressed Size 0000518B (20875) │ │ │ │ +2882B Filename Length 001D (29) │ │ │ │ +2882D Extra Length 001C (28) │ │ │ │ +2882F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2882F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2884C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2884E Length 0009 (9) │ │ │ │ +28850 Flags 03 (3) 'Modification Access' │ │ │ │ +28851 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +28855 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +28859 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2885B Length 000B (11) │ │ │ │ +2885D Version 01 (1) │ │ │ │ +2885E UID Size 04 (4) │ │ │ │ +2885F UID 00000000 (0) │ │ │ │ +28863 GID Size 04 (4) │ │ │ │ +28864 GID 00000000 (0) │ │ │ │ +28868 PAYLOAD │ │ │ │ │ │ │ │ 29D40 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ 29D44 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 29D45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 29D46 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 29D48 Compression Method 0008 (8) 'Deflated' │ │ │ │ -29D4A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -29D4E CRC 3C39EBFA (1010428922) │ │ │ │ -29D52 Compressed Size 00003805 (14341) │ │ │ │ +29D4A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +29D4E CRC 9734CAB9 (2536819385) │ │ │ │ +29D52 Compressed Size 00003803 (14339) │ │ │ │ 29D56 Uncompressed Size 0000EA4A (59978) │ │ │ │ 29D5A Filename Length 001C (28) │ │ │ │ 29D5C Extra Length 001C (28) │ │ │ │ 29D5E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x29D5E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 29D7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 29D7C Length 0009 (9) │ │ │ │ 29D7E Flags 03 (3) 'Modification Access' │ │ │ │ -29D7F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -29D83 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ +29D7F Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +29D83 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ 29D87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 29D89 Length 000B (11) │ │ │ │ 29D8B Version 01 (1) │ │ │ │ 29D8C UID Size 04 (4) │ │ │ │ 29D8D UID 00000000 (0) │ │ │ │ 29D91 GID Size 04 (4) │ │ │ │ 29D92 GID 00000000 (0) │ │ │ │ 29D96 PAYLOAD │ │ │ │ │ │ │ │ -2D59B LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -2D59F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2D5A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2D5A1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2D5A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2D5A5 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -2D5A9 CRC 9B224725 (2602714917) │ │ │ │ -2D5AD Compressed Size 0000069E (1694) │ │ │ │ -2D5B1 Uncompressed Size 000011F2 (4594) │ │ │ │ -2D5B5 Filename Length 001C (28) │ │ │ │ -2D5B7 Extra Length 001C (28) │ │ │ │ -2D5B9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2D5B9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2D5D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2D5D7 Length 0009 (9) │ │ │ │ -2D5D9 Flags 03 (3) 'Modification Access' │ │ │ │ -2D5DA Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2D5DE Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2D5E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2D5E4 Length 000B (11) │ │ │ │ -2D5E6 Version 01 (1) │ │ │ │ -2D5E7 UID Size 04 (4) │ │ │ │ -2D5E8 UID 00000000 (0) │ │ │ │ -2D5EC GID Size 04 (4) │ │ │ │ -2D5ED GID 00000000 (0) │ │ │ │ -2D5F1 PAYLOAD │ │ │ │ - │ │ │ │ -2DC8F LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -2DC93 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2DC94 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2DC95 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2DC97 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2DC99 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -2DC9D CRC D43062FF (3559940863) │ │ │ │ -2DCA1 Compressed Size 0000107A (4218) │ │ │ │ -2DCA5 Uncompressed Size 00004BFD (19453) │ │ │ │ -2DCA9 Filename Length 001B (27) │ │ │ │ -2DCAB Extra Length 001C (28) │ │ │ │ -2DCAD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2DCAD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2DCC8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2DCCA Length 0009 (9) │ │ │ │ -2DCCC Flags 03 (3) 'Modification Access' │ │ │ │ -2DCCD Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2DCD1 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2DCD5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2DCD7 Length 000B (11) │ │ │ │ -2DCD9 Version 01 (1) │ │ │ │ -2DCDA UID Size 04 (4) │ │ │ │ -2DCDB UID 00000000 (0) │ │ │ │ -2DCDF GID Size 04 (4) │ │ │ │ -2DCE0 GID 00000000 (0) │ │ │ │ -2DCE4 PAYLOAD │ │ │ │ - │ │ │ │ -2ED5E LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -2ED62 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2ED63 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2ED64 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2ED66 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2ED68 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -2ED6C CRC 62BD2C80 (1656564864) │ │ │ │ -2ED70 Compressed Size 00003BE0 (15328) │ │ │ │ -2ED74 Uncompressed Size 0000D781 (55169) │ │ │ │ -2ED78 Filename Length 001D (29) │ │ │ │ -2ED7A Extra Length 001C (28) │ │ │ │ -2ED7C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2ED7C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2ED99 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2ED9B Length 0009 (9) │ │ │ │ -2ED9D Flags 03 (3) 'Modification Access' │ │ │ │ -2ED9E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2EDA2 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -2EDA6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2EDA8 Length 000B (11) │ │ │ │ -2EDAA Version 01 (1) │ │ │ │ -2EDAB UID Size 04 (4) │ │ │ │ -2EDAC UID 00000000 (0) │ │ │ │ -2EDB0 GID Size 04 (4) │ │ │ │ -2EDB1 GID 00000000 (0) │ │ │ │ -2EDB5 PAYLOAD │ │ │ │ - │ │ │ │ -32995 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -32999 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3299A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3299B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3299D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3299F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -329A3 CRC E058F5F9 (3763926521) │ │ │ │ -329A7 Compressed Size 00000D67 (3431) │ │ │ │ -329AB Uncompressed Size 0000388B (14475) │ │ │ │ -329AF Filename Length 001D (29) │ │ │ │ -329B1 Extra Length 001C (28) │ │ │ │ -329B3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x329B3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -329D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -329D2 Length 0009 (9) │ │ │ │ -329D4 Flags 03 (3) 'Modification Access' │ │ │ │ -329D5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -329D9 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -329DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -329DF Length 000B (11) │ │ │ │ -329E1 Version 01 (1) │ │ │ │ -329E2 UID Size 04 (4) │ │ │ │ -329E3 UID 00000000 (0) │ │ │ │ -329E7 GID Size 04 (4) │ │ │ │ -329E8 GID 00000000 (0) │ │ │ │ -329EC PAYLOAD │ │ │ │ - │ │ │ │ -33753 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -33757 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -33758 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -33759 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3375B Compression Method 0008 (8) 'Deflated' │ │ │ │ -3375D Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -33761 CRC 142A122B (338301483) │ │ │ │ -33765 Compressed Size 00001C66 (7270) │ │ │ │ -33769 Uncompressed Size 0000C184 (49540) │ │ │ │ -3376D Filename Length 001A (26) │ │ │ │ -3376F Extra Length 001C (28) │ │ │ │ -33771 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33771: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3378B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3378D Length 0009 (9) │ │ │ │ -3378F Flags 03 (3) 'Modification Access' │ │ │ │ -33790 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -33794 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -33798 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3379A Length 000B (11) │ │ │ │ -3379C Version 01 (1) │ │ │ │ -3379D UID Size 04 (4) │ │ │ │ -3379E UID 00000000 (0) │ │ │ │ -337A2 GID Size 04 (4) │ │ │ │ -337A3 GID 00000000 (0) │ │ │ │ -337A7 PAYLOAD │ │ │ │ - │ │ │ │ -3540D LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -35411 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35412 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35413 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35415 Compression Method 0008 (8) 'Deflated' │ │ │ │ -35417 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3541B CRC 2BF4762B (737441323) │ │ │ │ -3541F Compressed Size 000003DE (990) │ │ │ │ -35423 Uncompressed Size 00000933 (2355) │ │ │ │ -35427 Filename Length 0012 (18) │ │ │ │ -35429 Extra Length 001C (28) │ │ │ │ -3542B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3542B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3543D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3543F Length 0009 (9) │ │ │ │ -35441 Flags 03 (3) 'Modification Access' │ │ │ │ -35442 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -35446 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3544A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3544C Length 000B (11) │ │ │ │ -3544E Version 01 (1) │ │ │ │ -3544F UID Size 04 (4) │ │ │ │ -35450 UID 00000000 (0) │ │ │ │ -35454 GID Size 04 (4) │ │ │ │ -35455 GID 00000000 (0) │ │ │ │ -35459 PAYLOAD │ │ │ │ - │ │ │ │ -35837 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -3583B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3583C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3583D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3583F Compression Method 0008 (8) 'Deflated' │ │ │ │ -35841 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -35845 CRC A4B161AE (2763088302) │ │ │ │ -35849 Compressed Size 000001D1 (465) │ │ │ │ -3584D Uncompressed Size 0000030F (783) │ │ │ │ -35851 Filename Length 0020 (32) │ │ │ │ -35853 Extra Length 001C (28) │ │ │ │ -35855 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35855: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35875 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35877 Length 0009 (9) │ │ │ │ -35879 Flags 03 (3) 'Modification Access' │ │ │ │ -3587A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3587E Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -35882 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35884 Length 000B (11) │ │ │ │ -35886 Version 01 (1) │ │ │ │ -35887 UID Size 04 (4) │ │ │ │ -35888 UID 00000000 (0) │ │ │ │ -3588C GID Size 04 (4) │ │ │ │ -3588D GID 00000000 (0) │ │ │ │ -35891 PAYLOAD │ │ │ │ - │ │ │ │ -35A62 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -35A66 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35A67 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35A68 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35A6A Compression Method 0008 (8) 'Deflated' │ │ │ │ -35A6C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -35A70 CRC 4DE68BB8 (1306954680) │ │ │ │ -35A74 Compressed Size 000017A6 (6054) │ │ │ │ -35A78 Uncompressed Size 00009D16 (40214) │ │ │ │ -35A7C Filename Length 001B (27) │ │ │ │ -35A7E Extra Length 001C (28) │ │ │ │ -35A80 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35A80: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35A9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35A9D Length 0009 (9) │ │ │ │ -35A9F Flags 03 (3) 'Modification Access' │ │ │ │ -35AA0 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -35AA4 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -35AA8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35AAA Length 000B (11) │ │ │ │ -35AAC Version 01 (1) │ │ │ │ -35AAD UID Size 04 (4) │ │ │ │ -35AAE UID 00000000 (0) │ │ │ │ -35AB2 GID Size 04 (4) │ │ │ │ -35AB3 GID 00000000 (0) │ │ │ │ -35AB7 PAYLOAD │ │ │ │ - │ │ │ │ -3725D LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -37261 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -37262 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -37263 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -37265 Compression Method 0008 (8) 'Deflated' │ │ │ │ -37267 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3726B CRC 1F5C05CF (526124495) │ │ │ │ -3726F Compressed Size 00001370 (4976) │ │ │ │ -37273 Uncompressed Size 00003B5F (15199) │ │ │ │ -37277 Filename Length 0015 (21) │ │ │ │ -37279 Extra Length 001C (28) │ │ │ │ -3727B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3727B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -37290 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -37292 Length 0009 (9) │ │ │ │ -37294 Flags 03 (3) 'Modification Access' │ │ │ │ -37295 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -37299 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3729D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3729F Length 000B (11) │ │ │ │ -372A1 Version 01 (1) │ │ │ │ -372A2 UID Size 04 (4) │ │ │ │ -372A3 UID 00000000 (0) │ │ │ │ -372A7 GID Size 04 (4) │ │ │ │ -372A8 GID 00000000 (0) │ │ │ │ -372AC PAYLOAD │ │ │ │ - │ │ │ │ -3861C LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -38620 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38621 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -38622 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -38624 Compression Method 0008 (8) 'Deflated' │ │ │ │ -38626 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3862A CRC ED1A23A7 (3977913255) │ │ │ │ -3862E Compressed Size 00000ACC (2764) │ │ │ │ -38632 Uncompressed Size 00002133 (8499) │ │ │ │ -38636 Filename Length 0011 (17) │ │ │ │ -38638 Extra Length 001C (28) │ │ │ │ -3863A Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3863A: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3864B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3864D Length 0009 (9) │ │ │ │ -3864F Flags 03 (3) 'Modification Access' │ │ │ │ -38650 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -38654 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -38658 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3865A Length 000B (11) │ │ │ │ -3865C Version 01 (1) │ │ │ │ -3865D UID Size 04 (4) │ │ │ │ -3865E UID 00000000 (0) │ │ │ │ -38662 GID Size 04 (4) │ │ │ │ -38663 GID 00000000 (0) │ │ │ │ -38667 PAYLOAD │ │ │ │ - │ │ │ │ -39133 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -39137 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -39138 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -39139 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3913B Compression Method 0008 (8) 'Deflated' │ │ │ │ -3913D Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -39141 CRC 8C273D06 (2351381766) │ │ │ │ -39145 Compressed Size 000003FD (1021) │ │ │ │ -39149 Uncompressed Size 00000F0A (3850) │ │ │ │ -3914D Filename Length 0014 (20) │ │ │ │ -3914F Extra Length 001C (28) │ │ │ │ -39151 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x39151: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -39165 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -39167 Length 0009 (9) │ │ │ │ -39169 Flags 03 (3) 'Modification Access' │ │ │ │ -3916A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3916E Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -39172 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -39174 Length 000B (11) │ │ │ │ -39176 Version 01 (1) │ │ │ │ -39177 UID Size 04 (4) │ │ │ │ -39178 UID 00000000 (0) │ │ │ │ -3917C GID Size 04 (4) │ │ │ │ -3917D GID 00000000 (0) │ │ │ │ -39181 PAYLOAD │ │ │ │ - │ │ │ │ -3957E LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ -39582 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -39583 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -39584 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -39586 Compression Method 0008 (8) 'Deflated' │ │ │ │ -39588 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3958C CRC A1F8FAEC (2717448940) │ │ │ │ -39590 Compressed Size 0000125F (4703) │ │ │ │ -39594 Uncompressed Size 00003467 (13415) │ │ │ │ -39598 Filename Length 0014 (20) │ │ │ │ -3959A Extra Length 001C (28) │ │ │ │ -3959C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3959C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -395B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -395B2 Length 0009 (9) │ │ │ │ -395B4 Flags 03 (3) 'Modification Access' │ │ │ │ -395B5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -395B9 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -395BD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -395BF Length 000B (11) │ │ │ │ -395C1 Version 01 (1) │ │ │ │ -395C2 UID Size 04 (4) │ │ │ │ -395C3 UID 00000000 (0) │ │ │ │ -395C7 GID Size 04 (4) │ │ │ │ -395C8 GID 00000000 (0) │ │ │ │ -395CC PAYLOAD │ │ │ │ - │ │ │ │ -3A82B LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -3A82F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3A830 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3A831 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3A833 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3A835 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3A839 CRC 089B1E28 (144383528) │ │ │ │ -3A83D Compressed Size 00000ACD (2765) │ │ │ │ -3A841 Uncompressed Size 000022FD (8957) │ │ │ │ -3A845 Filename Length 001B (27) │ │ │ │ -3A847 Extra Length 001C (28) │ │ │ │ -3A849 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3A849: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3A864 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3A866 Length 0009 (9) │ │ │ │ -3A868 Flags 03 (3) 'Modification Access' │ │ │ │ -3A869 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3A86D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3A871 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3A873 Length 000B (11) │ │ │ │ -3A875 Version 01 (1) │ │ │ │ -3A876 UID Size 04 (4) │ │ │ │ -3A877 UID 00000000 (0) │ │ │ │ -3A87B GID Size 04 (4) │ │ │ │ -3A87C GID 00000000 (0) │ │ │ │ -3A880 PAYLOAD │ │ │ │ - │ │ │ │ -3B34D LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ -3B351 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3B352 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3B353 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3B355 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3B357 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3B35B CRC 97366AE5 (2536925925) │ │ │ │ -3B35F Compressed Size 00000C4F (3151) │ │ │ │ -3B363 Uncompressed Size 0000273A (10042) │ │ │ │ -3B367 Filename Length 0013 (19) │ │ │ │ -3B369 Extra Length 001C (28) │ │ │ │ -3B36B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3B36B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3B37E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3B380 Length 0009 (9) │ │ │ │ -3B382 Flags 03 (3) 'Modification Access' │ │ │ │ -3B383 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3B387 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3B38B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3B38D Length 000B (11) │ │ │ │ -3B38F Version 01 (1) │ │ │ │ -3B390 UID Size 04 (4) │ │ │ │ -3B391 UID 00000000 (0) │ │ │ │ -3B395 GID Size 04 (4) │ │ │ │ -3B396 GID 00000000 (0) │ │ │ │ -3B39A PAYLOAD │ │ │ │ - │ │ │ │ -3BFE9 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ -3BFED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3BFEE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3BFEF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3BFF1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3BFF3 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3BFF7 CRC 275B1E57 (660282967) │ │ │ │ -3BFFB Compressed Size 00000C92 (3218) │ │ │ │ -3BFFF Uncompressed Size 00003D0E (15630) │ │ │ │ -3C003 Filename Length 0014 (20) │ │ │ │ -3C005 Extra Length 001C (28) │ │ │ │ -3C007 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C007: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C01B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C01D Length 0009 (9) │ │ │ │ -3C01F Flags 03 (3) 'Modification Access' │ │ │ │ -3C020 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3C024 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3C028 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C02A Length 000B (11) │ │ │ │ -3C02C Version 01 (1) │ │ │ │ -3C02D UID Size 04 (4) │ │ │ │ -3C02E UID 00000000 (0) │ │ │ │ -3C032 GID Size 04 (4) │ │ │ │ -3C033 GID 00000000 (0) │ │ │ │ -3C037 PAYLOAD │ │ │ │ - │ │ │ │ -3CCC9 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -3CCCD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3CCCE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3CCCF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3CCD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3CCD3 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3CCD7 CRC 7A7EC40C (2055128076) │ │ │ │ -3CCDB Compressed Size 00000F44 (3908) │ │ │ │ -3CCDF Uncompressed Size 00003742 (14146) │ │ │ │ -3CCE3 Filename Length 000F (15) │ │ │ │ -3CCE5 Extra Length 001C (28) │ │ │ │ -3CCE7 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3CCE7: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3CCF6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3CCF8 Length 0009 (9) │ │ │ │ -3CCFA Flags 03 (3) 'Modification Access' │ │ │ │ -3CCFB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3CCFF Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3CD03 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3CD05 Length 000B (11) │ │ │ │ -3CD07 Version 01 (1) │ │ │ │ -3CD08 UID Size 04 (4) │ │ │ │ -3CD09 UID 00000000 (0) │ │ │ │ -3CD0D GID Size 04 (4) │ │ │ │ -3CD0E GID 00000000 (0) │ │ │ │ -3CD12 PAYLOAD │ │ │ │ - │ │ │ │ -3DC56 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -3DC5A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3DC5B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3DC5C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3DC5E Compression Method 0008 (8) 'Deflated' │ │ │ │ -3DC60 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3DC64 CRC 5282A93D (1384294717) │ │ │ │ -3DC68 Compressed Size 000006B9 (1721) │ │ │ │ -3DC6C Uncompressed Size 00001A77 (6775) │ │ │ │ -3DC70 Filename Length 000F (15) │ │ │ │ -3DC72 Extra Length 001C (28) │ │ │ │ -3DC74 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3DC74: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3DC83 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3DC85 Length 0009 (9) │ │ │ │ -3DC87 Flags 03 (3) 'Modification Access' │ │ │ │ -3DC88 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3DC8C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3DC90 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3DC92 Length 000B (11) │ │ │ │ -3DC94 Version 01 (1) │ │ │ │ -3DC95 UID Size 04 (4) │ │ │ │ -3DC96 UID 00000000 (0) │ │ │ │ -3DC9A GID Size 04 (4) │ │ │ │ -3DC9B GID 00000000 (0) │ │ │ │ -3DC9F PAYLOAD │ │ │ │ - │ │ │ │ -3E358 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -3E35C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3E35D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3E35E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3E360 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3E362 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3E366 CRC 74F46BE4 (1962175460) │ │ │ │ -3E36A Compressed Size 00001A41 (6721) │ │ │ │ -3E36E Uncompressed Size 000064F8 (25848) │ │ │ │ -3E372 Filename Length 0013 (19) │ │ │ │ -3E374 Extra Length 001C (28) │ │ │ │ -3E376 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3E376: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3E389 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3E38B Length 0009 (9) │ │ │ │ -3E38D Flags 03 (3) 'Modification Access' │ │ │ │ -3E38E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3E392 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3E396 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3E398 Length 000B (11) │ │ │ │ -3E39A Version 01 (1) │ │ │ │ -3E39B UID Size 04 (4) │ │ │ │ -3E39C UID 00000000 (0) │ │ │ │ -3E3A0 GID Size 04 (4) │ │ │ │ -3E3A1 GID 00000000 (0) │ │ │ │ -3E3A5 PAYLOAD │ │ │ │ - │ │ │ │ -3FDE6 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -3FDEA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3FDEB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3FDEC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3FDEE Compression Method 0008 (8) 'Deflated' │ │ │ │ -3FDF0 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -3FDF4 CRC 5D9A5F08 (1570397960) │ │ │ │ -3FDF8 Compressed Size 000009A4 (2468) │ │ │ │ -3FDFC Uncompressed Size 00001B62 (7010) │ │ │ │ -3FE00 Filename Length 0010 (16) │ │ │ │ -3FE02 Extra Length 001C (28) │ │ │ │ -3FE04 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3FE04: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3FE14 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3FE16 Length 0009 (9) │ │ │ │ -3FE18 Flags 03 (3) 'Modification Access' │ │ │ │ -3FE19 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3FE1D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -3FE21 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3FE23 Length 000B (11) │ │ │ │ -3FE25 Version 01 (1) │ │ │ │ -3FE26 UID Size 04 (4) │ │ │ │ -3FE27 UID 00000000 (0) │ │ │ │ -3FE2B GID Size 04 (4) │ │ │ │ -3FE2C GID 00000000 (0) │ │ │ │ -3FE30 PAYLOAD │ │ │ │ - │ │ │ │ -407D4 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -407D8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -407D9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -407DA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -407DC Compression Method 0008 (8) 'Deflated' │ │ │ │ -407DE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -407E2 CRC 2F07E577 (789046647) │ │ │ │ -407E6 Compressed Size 000006B5 (1717) │ │ │ │ -407EA Uncompressed Size 00001563 (5475) │ │ │ │ -407EE Filename Length 0012 (18) │ │ │ │ -407F0 Extra Length 001C (28) │ │ │ │ -407F2 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x407F2: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40804 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40806 Length 0009 (9) │ │ │ │ -40808 Flags 03 (3) 'Modification Access' │ │ │ │ -40809 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -4080D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -40811 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40813 Length 000B (11) │ │ │ │ -40815 Version 01 (1) │ │ │ │ -40816 UID Size 04 (4) │ │ │ │ -40817 UID 00000000 (0) │ │ │ │ -4081B GID Size 04 (4) │ │ │ │ -4081C GID 00000000 (0) │ │ │ │ -40820 PAYLOAD │ │ │ │ - │ │ │ │ -40ED5 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -40ED9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -40EDA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -40EDB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -40EDD Compression Method 0008 (8) 'Deflated' │ │ │ │ -40EDF Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -40EE3 CRC 57512FDB (1464938459) │ │ │ │ -40EE7 Compressed Size 00002D61 (11617) │ │ │ │ -40EEB Uncompressed Size 0000D07C (53372) │ │ │ │ -40EEF Filename Length 0010 (16) │ │ │ │ -40EF1 Extra Length 001C (28) │ │ │ │ -40EF3 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x40EF3: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40F03 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40F05 Length 0009 (9) │ │ │ │ -40F07 Flags 03 (3) 'Modification Access' │ │ │ │ -40F08 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -40F0C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -40F10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40F12 Length 000B (11) │ │ │ │ -40F14 Version 01 (1) │ │ │ │ -40F15 UID Size 04 (4) │ │ │ │ -40F16 UID 00000000 (0) │ │ │ │ -40F1A GID Size 04 (4) │ │ │ │ -40F1B GID 00000000 (0) │ │ │ │ -40F1F PAYLOAD │ │ │ │ - │ │ │ │ -43C80 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -43C84 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -43C85 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -43C86 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -43C88 Compression Method 0008 (8) 'Deflated' │ │ │ │ -43C8A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -43C8E CRC 0F5DE7B2 (257812402) │ │ │ │ -43C92 Compressed Size 00001E87 (7815) │ │ │ │ -43C96 Uncompressed Size 00009AA8 (39592) │ │ │ │ -43C9A Filename Length 0012 (18) │ │ │ │ -43C9C Extra Length 001C (28) │ │ │ │ -43C9E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x43C9E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -43CB0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -43CB2 Length 0009 (9) │ │ │ │ -43CB4 Flags 03 (3) 'Modification Access' │ │ │ │ -43CB5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -43CB9 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -43CBD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -43CBF Length 000B (11) │ │ │ │ -43CC1 Version 01 (1) │ │ │ │ -43CC2 UID Size 04 (4) │ │ │ │ -43CC3 UID 00000000 (0) │ │ │ │ -43CC7 GID Size 04 (4) │ │ │ │ -43CC8 GID 00000000 (0) │ │ │ │ -43CCC PAYLOAD │ │ │ │ - │ │ │ │ -45B53 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -45B57 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -45B58 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -45B59 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -45B5B Compression Method 0008 (8) 'Deflated' │ │ │ │ -45B5D Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -45B61 CRC B2A9F717 (2997483287) │ │ │ │ -45B65 Compressed Size 00001471 (5233) │ │ │ │ -45B69 Uncompressed Size 00007ACD (31437) │ │ │ │ -45B6D Filename Length 0018 (24) │ │ │ │ -45B6F Extra Length 001C (28) │ │ │ │ -45B71 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45B71: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45B89 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -45B8B Length 0009 (9) │ │ │ │ -45B8D Flags 03 (3) 'Modification Access' │ │ │ │ -45B8E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -45B92 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -45B96 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45B98 Length 000B (11) │ │ │ │ -45B9A Version 01 (1) │ │ │ │ -45B9B UID Size 04 (4) │ │ │ │ -45B9C UID 00000000 (0) │ │ │ │ -45BA0 GID Size 04 (4) │ │ │ │ -45BA1 GID 00000000 (0) │ │ │ │ -45BA5 PAYLOAD │ │ │ │ - │ │ │ │ -47016 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -4701A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4701B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4701C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4701E Compression Method 0008 (8) 'Deflated' │ │ │ │ -47020 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -47024 CRC 12B9321E (314126878) │ │ │ │ -47028 Compressed Size 00002147 (8519) │ │ │ │ -4702C Uncompressed Size 0000CCBC (52412) │ │ │ │ -47030 Filename Length 001F (31) │ │ │ │ -47032 Extra Length 001C (28) │ │ │ │ -47034 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x47034: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -47053 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -47055 Length 0009 (9) │ │ │ │ -47057 Flags 03 (3) 'Modification Access' │ │ │ │ -47058 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -4705C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -47060 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -47062 Length 000B (11) │ │ │ │ -47064 Version 01 (1) │ │ │ │ -47065 UID Size 04 (4) │ │ │ │ -47066 UID 00000000 (0) │ │ │ │ -4706A GID Size 04 (4) │ │ │ │ -4706B GID 00000000 (0) │ │ │ │ -4706F PAYLOAD │ │ │ │ - │ │ │ │ -491B6 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -491BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -491BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -491BC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -491BE Compression Method 0008 (8) 'Deflated' │ │ │ │ -491C0 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -491C4 CRC 80CE55AF (2161005999) │ │ │ │ -491C8 Compressed Size 000003F5 (1013) │ │ │ │ -491CC Uncompressed Size 000008A1 (2209) │ │ │ │ -491D0 Filename Length 001E (30) │ │ │ │ -491D2 Extra Length 001C (28) │ │ │ │ -491D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x491D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -491F2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -491F4 Length 0009 (9) │ │ │ │ -491F6 Flags 03 (3) 'Modification Access' │ │ │ │ -491F7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -491FB Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -491FF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -49201 Length 000B (11) │ │ │ │ -49203 Version 01 (1) │ │ │ │ -49204 UID Size 04 (4) │ │ │ │ -49205 UID 00000000 (0) │ │ │ │ -49209 GID Size 04 (4) │ │ │ │ -4920A GID 00000000 (0) │ │ │ │ -4920E PAYLOAD │ │ │ │ - │ │ │ │ -49603 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -49607 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -49608 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -49609 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4960B Compression Method 0008 (8) 'Deflated' │ │ │ │ -4960D Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -49611 CRC DF319C26 (3744570406) │ │ │ │ -49615 Compressed Size 00004308 (17160) │ │ │ │ -49619 Uncompressed Size 0000DAC9 (56009) │ │ │ │ -4961D Filename Length 0013 (19) │ │ │ │ -4961F Extra Length 001C (28) │ │ │ │ -49621 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x49621: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -49634 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -49636 Length 0009 (9) │ │ │ │ -49638 Flags 03 (3) 'Modification Access' │ │ │ │ -49639 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -4963D Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -49641 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -49643 Length 000B (11) │ │ │ │ -49645 Version 01 (1) │ │ │ │ -49646 UID Size 04 (4) │ │ │ │ -49647 UID 00000000 (0) │ │ │ │ -4964B GID Size 04 (4) │ │ │ │ -4964C GID 00000000 (0) │ │ │ │ -49650 PAYLOAD │ │ │ │ - │ │ │ │ -4D958 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -4D95C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4D95D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4D95E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4D960 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4D962 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -4D966 CRC 571A6F3A (1461350202) │ │ │ │ -4D96A Compressed Size 000026C1 (9921) │ │ │ │ -4D96E Uncompressed Size 00006E43 (28227) │ │ │ │ -4D972 Filename Length 0019 (25) │ │ │ │ -4D974 Extra Length 001C (28) │ │ │ │ -4D976 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4D976: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4D98F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4D991 Length 0009 (9) │ │ │ │ -4D993 Flags 03 (3) 'Modification Access' │ │ │ │ -4D994 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -4D998 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -4D99C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4D99E Length 000B (11) │ │ │ │ -4D9A0 Version 01 (1) │ │ │ │ -4D9A1 UID Size 04 (4) │ │ │ │ -4D9A2 UID 00000000 (0) │ │ │ │ -4D9A6 GID Size 04 (4) │ │ │ │ -4D9A7 GID 00000000 (0) │ │ │ │ -4D9AB PAYLOAD │ │ │ │ - │ │ │ │ -5006C LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -50070 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -50071 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -50072 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -50074 Compression Method 0008 (8) 'Deflated' │ │ │ │ -50076 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -5007A CRC C06E9EF2 (3228475122) │ │ │ │ -5007E Compressed Size 0000273D (10045) │ │ │ │ -50082 Uncompressed Size 00008B81 (35713) │ │ │ │ -50086 Filename Length 0019 (25) │ │ │ │ -50088 Extra Length 001C (28) │ │ │ │ -5008A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5008A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -500A3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -500A5 Length 0009 (9) │ │ │ │ -500A7 Flags 03 (3) 'Modification Access' │ │ │ │ -500A8 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -500AC Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -500B0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -500B2 Length 000B (11) │ │ │ │ -500B4 Version 01 (1) │ │ │ │ -500B5 UID Size 04 (4) │ │ │ │ -500B6 UID 00000000 (0) │ │ │ │ -500BA GID Size 04 (4) │ │ │ │ -500BB GID 00000000 (0) │ │ │ │ -500BF PAYLOAD │ │ │ │ - │ │ │ │ -527FC LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -52800 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -52801 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -52802 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -52804 Compression Method 0008 (8) 'Deflated' │ │ │ │ -52806 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -5280A CRC AD016B67 (2902551399) │ │ │ │ -5280E Compressed Size 00000EC9 (3785) │ │ │ │ -52812 Uncompressed Size 000053BD (21437) │ │ │ │ -52816 Filename Length 0021 (33) │ │ │ │ -52818 Extra Length 001C (28) │ │ │ │ -5281A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5281A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5283B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5283D Length 0009 (9) │ │ │ │ -5283F Flags 03 (3) 'Modification Access' │ │ │ │ -52840 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -52844 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -52848 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5284A Length 000B (11) │ │ │ │ -5284C Version 01 (1) │ │ │ │ -5284D UID Size 04 (4) │ │ │ │ -5284E UID 00000000 (0) │ │ │ │ -52852 GID Size 04 (4) │ │ │ │ -52853 GID 00000000 (0) │ │ │ │ -52857 PAYLOAD │ │ │ │ - │ │ │ │ -53720 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -53724 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -53725 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -53726 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -53728 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5372A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -5372E CRC 7E4C0584 (2118911364) │ │ │ │ -53732 Compressed Size 00000534 (1332) │ │ │ │ -53736 Uncompressed Size 00000C94 (3220) │ │ │ │ -5373A Filename Length 0017 (23) │ │ │ │ -5373C Extra Length 001C (28) │ │ │ │ -5373E Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5373E: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -53755 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -53757 Length 0009 (9) │ │ │ │ -53759 Flags 03 (3) 'Modification Access' │ │ │ │ -5375A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -5375E Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -53762 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -53764 Length 000B (11) │ │ │ │ -53766 Version 01 (1) │ │ │ │ -53767 UID Size 04 (4) │ │ │ │ -53768 UID 00000000 (0) │ │ │ │ -5376C GID Size 04 (4) │ │ │ │ -5376D GID 00000000 (0) │ │ │ │ -53771 PAYLOAD │ │ │ │ - │ │ │ │ -53CA5 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -53CA9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -53CAA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -53CAB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -53CAD Compression Method 0008 (8) 'Deflated' │ │ │ │ -53CAF Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -53CB3 CRC 76893EF6 (1988706038) │ │ │ │ -53CB7 Compressed Size 00000465 (1125) │ │ │ │ -53CBB Uncompressed Size 0000092F (2351) │ │ │ │ -53CBF Filename Length 001B (27) │ │ │ │ -53CC1 Extra Length 001C (28) │ │ │ │ -53CC3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x53CC3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -53CDE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -53CE0 Length 0009 (9) │ │ │ │ -53CE2 Flags 03 (3) 'Modification Access' │ │ │ │ -53CE3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -53CE7 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -53CEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -53CED Length 000B (11) │ │ │ │ -53CEF Version 01 (1) │ │ │ │ -53CF0 UID Size 04 (4) │ │ │ │ -53CF1 UID 00000000 (0) │ │ │ │ -53CF5 GID Size 04 (4) │ │ │ │ -53CF6 GID 00000000 (0) │ │ │ │ -53CFA PAYLOAD │ │ │ │ - │ │ │ │ -5415F LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -54163 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -54164 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -54165 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -54167 Compression Method 0008 (8) 'Deflated' │ │ │ │ -54169 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -5416D CRC A30F00DB (2735669467) │ │ │ │ -54171 Compressed Size 000016F7 (5879) │ │ │ │ -54175 Uncompressed Size 00007A6B (31339) │ │ │ │ -54179 Filename Length 001F (31) │ │ │ │ -5417B Extra Length 001C (28) │ │ │ │ -5417D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5417D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5419C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5419E Length 0009 (9) │ │ │ │ -541A0 Flags 03 (3) 'Modification Access' │ │ │ │ -541A1 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -541A5 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -541A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -541AB Length 000B (11) │ │ │ │ -541AD Version 01 (1) │ │ │ │ -541AE UID Size 04 (4) │ │ │ │ -541AF UID 00000000 (0) │ │ │ │ -541B3 GID Size 04 (4) │ │ │ │ -541B4 GID 00000000 (0) │ │ │ │ -541B8 PAYLOAD │ │ │ │ - │ │ │ │ -558AF LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -558B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -558B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -558B5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -558B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -558B9 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -558BD CRC 9EFDD8DE (2667436254) │ │ │ │ -558C1 Compressed Size 00004161 (16737) │ │ │ │ -558C5 Uncompressed Size 0001D15D (119133) │ │ │ │ -558C9 Filename Length 0010 (16) │ │ │ │ -558CB Extra Length 001C (28) │ │ │ │ -558CD Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x558CD: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -558DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -558DF Length 0009 (9) │ │ │ │ -558E1 Flags 03 (3) 'Modification Access' │ │ │ │ -558E2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -558E6 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -558EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -558EC Length 000B (11) │ │ │ │ -558EE Version 01 (1) │ │ │ │ -558EF UID Size 04 (4) │ │ │ │ -558F0 UID 00000000 (0) │ │ │ │ -558F4 GID Size 04 (4) │ │ │ │ -558F5 GID 00000000 (0) │ │ │ │ -558F9 PAYLOAD │ │ │ │ - │ │ │ │ -59A5A LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -59A5E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -59A5F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -59A60 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -59A62 Compression Method 0008 (8) 'Deflated' │ │ │ │ -59A64 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -59A68 CRC 5C6B0F1E (1550520094) │ │ │ │ -59A6C Compressed Size 00000A93 (2707) │ │ │ │ -59A70 Uncompressed Size 00002103 (8451) │ │ │ │ -59A74 Filename Length 0014 (20) │ │ │ │ -59A76 Extra Length 001C (28) │ │ │ │ -59A78 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x59A78: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -59A8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -59A8E Length 0009 (9) │ │ │ │ -59A90 Flags 03 (3) 'Modification Access' │ │ │ │ -59A91 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -59A95 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -59A99 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -59A9B Length 000B (11) │ │ │ │ -59A9D Version 01 (1) │ │ │ │ -59A9E UID Size 04 (4) │ │ │ │ -59A9F UID 00000000 (0) │ │ │ │ -59AA3 GID Size 04 (4) │ │ │ │ -59AA4 GID 00000000 (0) │ │ │ │ -59AA8 PAYLOAD │ │ │ │ - │ │ │ │ -5A53B LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -5A53F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5A540 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5A541 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5A543 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5A545 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -5A549 CRC C83C6772 (3359401842) │ │ │ │ -5A54D Compressed Size 0000B53A (46394) │ │ │ │ -5A551 Uncompressed Size 000418F9 (268537) │ │ │ │ -5A555 Filename Length 0017 (23) │ │ │ │ -5A557 Extra Length 001C (28) │ │ │ │ -5A559 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5A559: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5A570 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5A572 Length 0009 (9) │ │ │ │ -5A574 Flags 03 (3) 'Modification Access' │ │ │ │ -5A575 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -5A579 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -5A57D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5A57F Length 000B (11) │ │ │ │ -5A581 Version 01 (1) │ │ │ │ -5A582 UID Size 04 (4) │ │ │ │ -5A583 UID 00000000 (0) │ │ │ │ -5A587 GID Size 04 (4) │ │ │ │ -5A588 GID 00000000 (0) │ │ │ │ -5A58C PAYLOAD │ │ │ │ - │ │ │ │ -65AC6 LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -65ACA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -65ACB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -65ACC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -65ACE Compression Method 0008 (8) 'Deflated' │ │ │ │ -65AD0 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -65AD4 CRC 017E6141 (25059649) │ │ │ │ -65AD8 Compressed Size 000003FE (1022) │ │ │ │ -65ADC Uncompressed Size 0000093B (2363) │ │ │ │ -65AE0 Filename Length 0013 (19) │ │ │ │ -65AE2 Extra Length 001C (28) │ │ │ │ -65AE4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x65AE4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -65AF7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -65AF9 Length 0009 (9) │ │ │ │ -65AFB Flags 03 (3) 'Modification Access' │ │ │ │ -65AFC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -65B00 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -65B04 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -65B06 Length 000B (11) │ │ │ │ -65B08 Version 01 (1) │ │ │ │ -65B09 UID Size 04 (4) │ │ │ │ -65B0A UID 00000000 (0) │ │ │ │ -65B0E GID Size 04 (4) │ │ │ │ -65B0F GID 00000000 (0) │ │ │ │ -65B13 PAYLOAD │ │ │ │ - │ │ │ │ -65F11 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -65F15 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -65F16 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -65F17 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -65F19 Compression Method 0008 (8) 'Deflated' │ │ │ │ -65F1B Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -65F1F CRC 0716D135 (118935861) │ │ │ │ -65F23 Compressed Size 000014DB (5339) │ │ │ │ -65F27 Uncompressed Size 00006890 (26768) │ │ │ │ -65F2B Filename Length 0012 (18) │ │ │ │ -65F2D Extra Length 001C (28) │ │ │ │ -65F2F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x65F2F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -65F41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -65F43 Length 0009 (9) │ │ │ │ -65F45 Flags 03 (3) 'Modification Access' │ │ │ │ -65F46 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -65F4A Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -65F4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -65F50 Length 000B (11) │ │ │ │ -65F52 Version 01 (1) │ │ │ │ -65F53 UID Size 04 (4) │ │ │ │ -65F54 UID 00000000 (0) │ │ │ │ -65F58 GID Size 04 (4) │ │ │ │ -65F59 GID 00000000 (0) │ │ │ │ -65F5D PAYLOAD │ │ │ │ - │ │ │ │ -67438 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -6743C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6743D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6743E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -67440 Compression Method 0008 (8) 'Deflated' │ │ │ │ -67442 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -67446 CRC 0C854523 (210060579) │ │ │ │ -6744A Compressed Size 000011EB (4587) │ │ │ │ -6744E Uncompressed Size 000040F8 (16632) │ │ │ │ -67452 Filename Length 0012 (18) │ │ │ │ -67454 Extra Length 001C (28) │ │ │ │ -67456 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x67456: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -67468 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6746A Length 0009 (9) │ │ │ │ -6746C Flags 03 (3) 'Modification Access' │ │ │ │ -6746D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -67471 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -67475 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -67477 Length 000B (11) │ │ │ │ -67479 Version 01 (1) │ │ │ │ -6747A UID Size 04 (4) │ │ │ │ -6747B UID 00000000 (0) │ │ │ │ -6747F GID Size 04 (4) │ │ │ │ -67480 GID 00000000 (0) │ │ │ │ -67484 PAYLOAD │ │ │ │ - │ │ │ │ -6866F LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -68673 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -68674 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -68675 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -68677 Compression Method 0008 (8) 'Deflated' │ │ │ │ -68679 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6867D CRC 3C82A845 (1015195717) │ │ │ │ -68681 Compressed Size 000009D8 (2520) │ │ │ │ -68685 Uncompressed Size 00003527 (13607) │ │ │ │ -68689 Filename Length 0019 (25) │ │ │ │ -6868B Extra Length 001C (28) │ │ │ │ -6868D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6868D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -686A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -686A8 Length 0009 (9) │ │ │ │ -686AA Flags 03 (3) 'Modification Access' │ │ │ │ -686AB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -686AF Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -686B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -686B5 Length 000B (11) │ │ │ │ -686B7 Version 01 (1) │ │ │ │ -686B8 UID Size 04 (4) │ │ │ │ -686B9 UID 00000000 (0) │ │ │ │ -686BD GID Size 04 (4) │ │ │ │ -686BE GID 00000000 (0) │ │ │ │ -686C2 PAYLOAD │ │ │ │ - │ │ │ │ -6909A LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -6909E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6909F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -690A0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -690A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -690A4 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -690A8 CRC 7154FE10 (1901395472) │ │ │ │ -690AC Compressed Size 000018B3 (6323) │ │ │ │ -690B0 Uncompressed Size 0000A676 (42614) │ │ │ │ -690B4 Filename Length 0019 (25) │ │ │ │ -690B6 Extra Length 001C (28) │ │ │ │ -690B8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x690B8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -690D1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -690D3 Length 0009 (9) │ │ │ │ -690D5 Flags 03 (3) 'Modification Access' │ │ │ │ -690D6 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -690DA Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -690DE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -690E0 Length 000B (11) │ │ │ │ -690E2 Version 01 (1) │ │ │ │ -690E3 UID Size 04 (4) │ │ │ │ -690E4 UID 00000000 (0) │ │ │ │ -690E8 GID Size 04 (4) │ │ │ │ -690E9 GID 00000000 (0) │ │ │ │ -690ED PAYLOAD │ │ │ │ - │ │ │ │ -6A9A0 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -6A9A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6A9A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6A9A6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6A9A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6A9AA Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6A9AE CRC 28DDA60C (685614604) │ │ │ │ -6A9B2 Compressed Size 0000177A (6010) │ │ │ │ -6A9B6 Uncompressed Size 0000472A (18218) │ │ │ │ -6A9BA Filename Length 0014 (20) │ │ │ │ -6A9BC Extra Length 001C (28) │ │ │ │ -6A9BE Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6A9BE: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6A9D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6A9D4 Length 0009 (9) │ │ │ │ -6A9D6 Flags 03 (3) 'Modification Access' │ │ │ │ -6A9D7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6A9DB Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6A9DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6A9E1 Length 000B (11) │ │ │ │ -6A9E3 Version 01 (1) │ │ │ │ -6A9E4 UID Size 04 (4) │ │ │ │ -6A9E5 UID 00000000 (0) │ │ │ │ -6A9E9 GID Size 04 (4) │ │ │ │ -6A9EA GID 00000000 (0) │ │ │ │ -6A9EE PAYLOAD │ │ │ │ - │ │ │ │ -6C168 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -6C16C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6C16D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6C16E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6C170 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6C172 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6C176 CRC AAF8364A (2868393546) │ │ │ │ -6C17A Compressed Size 00000408 (1032) │ │ │ │ -6C17E Uncompressed Size 00000823 (2083) │ │ │ │ -6C182 Filename Length 001C (28) │ │ │ │ -6C184 Extra Length 001C (28) │ │ │ │ -6C186 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6C186: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6C1A2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6C1A4 Length 0009 (9) │ │ │ │ -6C1A6 Flags 03 (3) 'Modification Access' │ │ │ │ -6C1A7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6C1AB Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6C1AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6C1B1 Length 000B (11) │ │ │ │ -6C1B3 Version 01 (1) │ │ │ │ -6C1B4 UID Size 04 (4) │ │ │ │ -6C1B5 UID 00000000 (0) │ │ │ │ -6C1B9 GID Size 04 (4) │ │ │ │ -6C1BA GID 00000000 (0) │ │ │ │ -6C1BE PAYLOAD │ │ │ │ - │ │ │ │ -6C5C6 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -6C5CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6C5CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6C5CC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6C5CE Compression Method 0008 (8) 'Deflated' │ │ │ │ -6C5D0 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6C5D4 CRC 50BEED75 (1354689909) │ │ │ │ -6C5D8 Compressed Size 000024BE (9406) │ │ │ │ -6C5DC Uncompressed Size 0000B659 (46681) │ │ │ │ -6C5E0 Filename Length 001F (31) │ │ │ │ -6C5E2 Extra Length 001C (28) │ │ │ │ -6C5E4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6C5E4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6C603 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6C605 Length 0009 (9) │ │ │ │ -6C607 Flags 03 (3) 'Modification Access' │ │ │ │ -6C608 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6C60C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6C610 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6C612 Length 000B (11) │ │ │ │ -6C614 Version 01 (1) │ │ │ │ -6C615 UID Size 04 (4) │ │ │ │ -6C616 UID 00000000 (0) │ │ │ │ -6C61A GID Size 04 (4) │ │ │ │ -6C61B GID 00000000 (0) │ │ │ │ -6C61F PAYLOAD │ │ │ │ - │ │ │ │ -6EADD LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -6EAE1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6EAE2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6EAE3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6EAE5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6EAE7 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6EAEB CRC 6C44BBE5 (1816443877) │ │ │ │ -6EAEF Compressed Size 00000E7B (3707) │ │ │ │ -6EAF3 Uncompressed Size 000052D7 (21207) │ │ │ │ -6EAF7 Filename Length 001F (31) │ │ │ │ -6EAF9 Extra Length 001C (28) │ │ │ │ -6EAFB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6EAFB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6EB1A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6EB1C Length 0009 (9) │ │ │ │ -6EB1E Flags 03 (3) 'Modification Access' │ │ │ │ -6EB1F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6EB23 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6EB27 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6EB29 Length 000B (11) │ │ │ │ -6EB2B Version 01 (1) │ │ │ │ -6EB2C UID Size 04 (4) │ │ │ │ -6EB2D UID 00000000 (0) │ │ │ │ -6EB31 GID Size 04 (4) │ │ │ │ -6EB32 GID 00000000 (0) │ │ │ │ -6EB36 PAYLOAD │ │ │ │ - │ │ │ │ -6F9B1 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -6F9B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6F9B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6F9B7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6F9B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6F9BB Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -6F9BF CRC 337E1414 (863900692) │ │ │ │ -6F9C3 Compressed Size 00000A42 (2626) │ │ │ │ -6F9C7 Uncompressed Size 00002478 (9336) │ │ │ │ -6F9CB Filename Length 0013 (19) │ │ │ │ -6F9CD Extra Length 001C (28) │ │ │ │ -6F9CF Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6F9CF: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6F9E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6F9E4 Length 0009 (9) │ │ │ │ -6F9E6 Flags 03 (3) 'Modification Access' │ │ │ │ -6F9E7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6F9EB Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -6F9EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6F9F1 Length 000B (11) │ │ │ │ -6F9F3 Version 01 (1) │ │ │ │ -6F9F4 UID Size 04 (4) │ │ │ │ -6F9F5 UID 00000000 (0) │ │ │ │ -6F9F9 GID Size 04 (4) │ │ │ │ -6F9FA GID 00000000 (0) │ │ │ │ -6F9FE PAYLOAD │ │ │ │ - │ │ │ │ -70440 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -70444 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -70445 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -70446 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -70448 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7044A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -7044E CRC F602667E (4127352446) │ │ │ │ -70452 Compressed Size 00002549 (9545) │ │ │ │ -70456 Uncompressed Size 0000B9C7 (47559) │ │ │ │ -7045A Filename Length 0019 (25) │ │ │ │ -7045C Extra Length 001C (28) │ │ │ │ -7045E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7045E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -70477 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -70479 Length 0009 (9) │ │ │ │ -7047B Flags 03 (3) 'Modification Access' │ │ │ │ -7047C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -70480 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -70484 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -70486 Length 000B (11) │ │ │ │ -70488 Version 01 (1) │ │ │ │ -70489 UID Size 04 (4) │ │ │ │ -7048A UID 00000000 (0) │ │ │ │ -7048E GID Size 04 (4) │ │ │ │ -7048F GID 00000000 (0) │ │ │ │ -70493 PAYLOAD │ │ │ │ - │ │ │ │ -729DC LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -729E0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -729E1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -729E2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -729E4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -729E6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -729EA CRC 0EF470FF (250900735) │ │ │ │ -729EE Compressed Size 00000EF8 (3832) │ │ │ │ -729F2 Uncompressed Size 00003A2A (14890) │ │ │ │ -729F6 Filename Length 0024 (36) │ │ │ │ -729F8 Extra Length 001C (28) │ │ │ │ -729FA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x729FA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -72A1E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -72A20 Length 0009 (9) │ │ │ │ -72A22 Flags 03 (3) 'Modification Access' │ │ │ │ -72A23 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -72A27 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -72A2B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -72A2D Length 000B (11) │ │ │ │ -72A2F Version 01 (1) │ │ │ │ -72A30 UID Size 04 (4) │ │ │ │ -72A31 UID 00000000 (0) │ │ │ │ -72A35 GID Size 04 (4) │ │ │ │ -72A36 GID 00000000 (0) │ │ │ │ -72A3A PAYLOAD │ │ │ │ - │ │ │ │ -73932 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -73936 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -73937 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -73938 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7393A Compression Method 0008 (8) 'Deflated' │ │ │ │ -7393C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -73940 CRC AA7C1B5F (2860260191) │ │ │ │ -73944 Compressed Size 00001AB3 (6835) │ │ │ │ -73948 Uncompressed Size 00005F00 (24320) │ │ │ │ -7394C Filename Length 0017 (23) │ │ │ │ -7394E Extra Length 001C (28) │ │ │ │ -73950 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x73950: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -73967 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -73969 Length 0009 (9) │ │ │ │ -7396B Flags 03 (3) 'Modification Access' │ │ │ │ -7396C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -73970 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -73974 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -73976 Length 000B (11) │ │ │ │ -73978 Version 01 (1) │ │ │ │ -73979 UID Size 04 (4) │ │ │ │ -7397A UID 00000000 (0) │ │ │ │ -7397E GID Size 04 (4) │ │ │ │ -7397F GID 00000000 (0) │ │ │ │ -73983 PAYLOAD │ │ │ │ - │ │ │ │ -75436 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -7543A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7543B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7543C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7543E Compression Method 0008 (8) 'Deflated' │ │ │ │ -75440 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -75444 CRC 11E32AF1 (300100337) │ │ │ │ -75448 Compressed Size 00000ED3 (3795) │ │ │ │ -7544C Uncompressed Size 000038E2 (14562) │ │ │ │ -75450 Filename Length 0023 (35) │ │ │ │ -75452 Extra Length 001C (28) │ │ │ │ -75454 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x75454: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -75477 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -75479 Length 0009 (9) │ │ │ │ -7547B Flags 03 (3) 'Modification Access' │ │ │ │ -7547C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -75480 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -75484 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -75486 Length 000B (11) │ │ │ │ -75488 Version 01 (1) │ │ │ │ -75489 UID Size 04 (4) │ │ │ │ -7548A UID 00000000 (0) │ │ │ │ -7548E GID Size 04 (4) │ │ │ │ -7548F GID 00000000 (0) │ │ │ │ -75493 PAYLOAD │ │ │ │ - │ │ │ │ -76366 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -7636A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7636B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7636C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7636E Compression Method 0008 (8) 'Deflated' │ │ │ │ -76370 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -76374 CRC 2DB7929F (767005343) │ │ │ │ -76378 Compressed Size 00000113 (275) │ │ │ │ -7637C Uncompressed Size 000001F3 (499) │ │ │ │ -76380 Filename Length 001B (27) │ │ │ │ -76382 Extra Length 001C (28) │ │ │ │ -76384 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x76384: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7639F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -763A1 Length 0009 (9) │ │ │ │ -763A3 Flags 03 (3) 'Modification Access' │ │ │ │ -763A4 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -763A8 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -763AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -763AE Length 000B (11) │ │ │ │ -763B0 Version 01 (1) │ │ │ │ -763B1 UID Size 04 (4) │ │ │ │ -763B2 UID 00000000 (0) │ │ │ │ -763B6 GID Size 04 (4) │ │ │ │ -763B7 GID 00000000 (0) │ │ │ │ -763BB PAYLOAD │ │ │ │ - │ │ │ │ -764CE LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -764D2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -764D3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -764D4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -764D6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -764D8 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -764DC CRC 1466816A (342262122) │ │ │ │ -764E0 Compressed Size 00001890 (6288) │ │ │ │ -764E4 Uncompressed Size 00008FAA (36778) │ │ │ │ -764E8 Filename Length 001D (29) │ │ │ │ -764EA Extra Length 001C (28) │ │ │ │ -764EC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x764EC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -76509 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7650B Length 0009 (9) │ │ │ │ -7650D Flags 03 (3) 'Modification Access' │ │ │ │ -7650E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -76512 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -76516 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -76518 Length 000B (11) │ │ │ │ -7651A Version 01 (1) │ │ │ │ -7651B UID Size 04 (4) │ │ │ │ -7651C UID 00000000 (0) │ │ │ │ -76520 GID Size 04 (4) │ │ │ │ -76521 GID 00000000 (0) │ │ │ │ -76525 PAYLOAD │ │ │ │ - │ │ │ │ -77DB5 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -77DB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -77DBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -77DBB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -77DBD Compression Method 0008 (8) 'Deflated' │ │ │ │ -77DBF Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -77DC3 CRC 227D9DE1 (578657761) │ │ │ │ -77DC7 Compressed Size 0000164B (5707) │ │ │ │ -77DCB Uncompressed Size 00003A99 (15001) │ │ │ │ -77DCF Filename Length 0015 (21) │ │ │ │ -77DD1 Extra Length 001C (28) │ │ │ │ -77DD3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x77DD3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -77DE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -77DEA Length 0009 (9) │ │ │ │ -77DEC Flags 03 (3) 'Modification Access' │ │ │ │ -77DED Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -77DF1 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -77DF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -77DF7 Length 000B (11) │ │ │ │ -77DF9 Version 01 (1) │ │ │ │ -77DFA UID Size 04 (4) │ │ │ │ -77DFB UID 00000000 (0) │ │ │ │ -77DFF GID Size 04 (4) │ │ │ │ -77E00 GID 00000000 (0) │ │ │ │ -77E04 PAYLOAD │ │ │ │ - │ │ │ │ -7944F LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -79453 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -79454 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -79455 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -79457 Compression Method 0008 (8) 'Deflated' │ │ │ │ -79459 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -7945D CRC CEAA5B5B (3467271003) │ │ │ │ -79461 Compressed Size 00003D38 (15672) │ │ │ │ -79465 Uncompressed Size 0001219B (74139) │ │ │ │ -79469 Filename Length 0016 (22) │ │ │ │ -7946B Extra Length 001C (28) │ │ │ │ -7946D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7946D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -79483 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -79485 Length 0009 (9) │ │ │ │ -79487 Flags 03 (3) 'Modification Access' │ │ │ │ -79488 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -7948C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -79490 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -79492 Length 000B (11) │ │ │ │ -79494 Version 01 (1) │ │ │ │ -79495 UID Size 04 (4) │ │ │ │ -79496 UID 00000000 (0) │ │ │ │ -7949A GID Size 04 (4) │ │ │ │ -7949B GID 00000000 (0) │ │ │ │ -7949F PAYLOAD │ │ │ │ - │ │ │ │ -7D1D7 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -7D1DB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7D1DC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7D1DD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7D1DF Compression Method 0008 (8) 'Deflated' │ │ │ │ -7D1E1 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -7D1E5 CRC E619BA48 (3860445768) │ │ │ │ -7D1E9 Compressed Size 00003E85 (16005) │ │ │ │ -7D1ED Uncompressed Size 0001C179 (115065) │ │ │ │ -7D1F1 Filename Length 0019 (25) │ │ │ │ -7D1F3 Extra Length 001C (28) │ │ │ │ -7D1F5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7D1F5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7D20E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7D210 Length 0009 (9) │ │ │ │ -7D212 Flags 03 (3) 'Modification Access' │ │ │ │ -7D213 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -7D217 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -7D21B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7D21D Length 000B (11) │ │ │ │ -7D21F Version 01 (1) │ │ │ │ -7D220 UID Size 04 (4) │ │ │ │ -7D221 UID 00000000 (0) │ │ │ │ -7D225 GID Size 04 (4) │ │ │ │ -7D226 GID 00000000 (0) │ │ │ │ -7D22A PAYLOAD │ │ │ │ - │ │ │ │ -810AF LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ -810B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -810B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -810B5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -810B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -810B9 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -810BD CRC E446B5DF (3829839327) │ │ │ │ -810C1 Compressed Size 0000088A (2186) │ │ │ │ -810C5 Uncompressed Size 0000362C (13868) │ │ │ │ -810C9 Filename Length 0011 (17) │ │ │ │ -810CB Extra Length 001C (28) │ │ │ │ -810CD Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x810CD: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -810DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -810E0 Length 0009 (9) │ │ │ │ -810E2 Flags 03 (3) 'Modification Access' │ │ │ │ -810E3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -810E7 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -810EB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -810ED Length 000B (11) │ │ │ │ -810EF Version 01 (1) │ │ │ │ -810F0 UID Size 04 (4) │ │ │ │ -810F1 UID 00000000 (0) │ │ │ │ -810F5 GID Size 04 (4) │ │ │ │ -810F6 GID 00000000 (0) │ │ │ │ -810FA PAYLOAD │ │ │ │ - │ │ │ │ -81984 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -81988 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -81989 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8198A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8198C Compression Method 0008 (8) 'Deflated' │ │ │ │ -8198E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -81992 CRC 3249F529 (843707689) │ │ │ │ -81996 Compressed Size 000051B7 (20919) │ │ │ │ -8199A Uncompressed Size 0001FBDD (130013) │ │ │ │ -8199E Filename Length 0015 (21) │ │ │ │ -819A0 Extra Length 001C (28) │ │ │ │ -819A2 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x819A2: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -819B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -819B9 Length 0009 (9) │ │ │ │ -819BB Flags 03 (3) 'Modification Access' │ │ │ │ -819BC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -819C0 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -819C4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -819C6 Length 000B (11) │ │ │ │ -819C8 Version 01 (1) │ │ │ │ -819C9 UID Size 04 (4) │ │ │ │ -819CA UID 00000000 (0) │ │ │ │ -819CE GID Size 04 (4) │ │ │ │ -819CF GID 00000000 (0) │ │ │ │ -819D3 PAYLOAD │ │ │ │ - │ │ │ │ -86B8A LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -86B8E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -86B8F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -86B90 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -86B92 Compression Method 0008 (8) 'Deflated' │ │ │ │ -86B94 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -86B98 CRC 91C1F665 (2445407845) │ │ │ │ -86B9C Compressed Size 00001AFA (6906) │ │ │ │ -86BA0 Uncompressed Size 00008257 (33367) │ │ │ │ -86BA4 Filename Length 0019 (25) │ │ │ │ -86BA6 Extra Length 001C (28) │ │ │ │ -86BA8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x86BA8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -86BC1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -86BC3 Length 0009 (9) │ │ │ │ -86BC5 Flags 03 (3) 'Modification Access' │ │ │ │ -86BC6 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -86BCA Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -86BCE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -86BD0 Length 000B (11) │ │ │ │ -86BD2 Version 01 (1) │ │ │ │ -86BD3 UID Size 04 (4) │ │ │ │ -86BD4 UID 00000000 (0) │ │ │ │ -86BD8 GID Size 04 (4) │ │ │ │ -86BD9 GID 00000000 (0) │ │ │ │ -86BDD PAYLOAD │ │ │ │ - │ │ │ │ -886D7 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -886DB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -886DC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -886DD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -886DF Compression Method 0008 (8) 'Deflated' │ │ │ │ -886E1 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -886E5 CRC A5421022 (2772570146) │ │ │ │ -886E9 Compressed Size 00000D95 (3477) │ │ │ │ -886ED Uncompressed Size 00002E9D (11933) │ │ │ │ -886F1 Filename Length 0018 (24) │ │ │ │ -886F3 Extra Length 001C (28) │ │ │ │ -886F5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x886F5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8870D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8870F Length 0009 (9) │ │ │ │ -88711 Flags 03 (3) 'Modification Access' │ │ │ │ -88712 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -88716 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8871A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8871C Length 000B (11) │ │ │ │ -8871E Version 01 (1) │ │ │ │ -8871F UID Size 04 (4) │ │ │ │ -88720 UID 00000000 (0) │ │ │ │ -88724 GID Size 04 (4) │ │ │ │ -88725 GID 00000000 (0) │ │ │ │ -88729 PAYLOAD │ │ │ │ - │ │ │ │ -894BE LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -894C2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -894C3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -894C4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -894C6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -894C8 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -894CC CRC 42771396 (1115100054) │ │ │ │ -894D0 Compressed Size 000001DF (479) │ │ │ │ -894D4 Uncompressed Size 00000321 (801) │ │ │ │ -894D8 Filename Length 0011 (17) │ │ │ │ -894DA Extra Length 001C (28) │ │ │ │ -894DC Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x894DC: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -894ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -894EF Length 0009 (9) │ │ │ │ -894F1 Flags 03 (3) 'Modification Access' │ │ │ │ -894F2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -894F6 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -894FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -894FC Length 000B (11) │ │ │ │ -894FE Version 01 (1) │ │ │ │ -894FF UID Size 04 (4) │ │ │ │ -89500 UID 00000000 (0) │ │ │ │ -89504 GID Size 04 (4) │ │ │ │ -89505 GID 00000000 (0) │ │ │ │ -89509 PAYLOAD │ │ │ │ - │ │ │ │ -896E8 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -896EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -896ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -896EE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -896F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -896F2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -896F6 CRC DB0CDFF8 (3675054072) │ │ │ │ -896FA Compressed Size 000006BC (1724) │ │ │ │ -896FE Uncompressed Size 0000141C (5148) │ │ │ │ -89702 Filename Length 0019 (25) │ │ │ │ -89704 Extra Length 001C (28) │ │ │ │ -89706 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x89706: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8971F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -89721 Length 0009 (9) │ │ │ │ -89723 Flags 03 (3) 'Modification Access' │ │ │ │ -89724 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -89728 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8972C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8972E Length 000B (11) │ │ │ │ -89730 Version 01 (1) │ │ │ │ -89731 UID Size 04 (4) │ │ │ │ -89732 UID 00000000 (0) │ │ │ │ -89736 GID Size 04 (4) │ │ │ │ -89737 GID 00000000 (0) │ │ │ │ -8973B PAYLOAD │ │ │ │ - │ │ │ │ -89DF7 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -89DFB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -89DFC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -89DFD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -89DFF Compression Method 0008 (8) 'Deflated' │ │ │ │ -89E01 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -89E05 CRC 6202EE3B (1644359227) │ │ │ │ -89E09 Compressed Size 00001B8A (7050) │ │ │ │ -89E0D Uncompressed Size 00009F5D (40797) │ │ │ │ -89E11 Filename Length 0018 (24) │ │ │ │ -89E13 Extra Length 001C (28) │ │ │ │ -89E15 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x89E15: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -89E2D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -89E2F Length 0009 (9) │ │ │ │ -89E31 Flags 03 (3) 'Modification Access' │ │ │ │ -89E32 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -89E36 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -89E3A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -89E3C Length 000B (11) │ │ │ │ -89E3E Version 01 (1) │ │ │ │ -89E3F UID Size 04 (4) │ │ │ │ -89E40 UID 00000000 (0) │ │ │ │ -89E44 GID Size 04 (4) │ │ │ │ -89E45 GID 00000000 (0) │ │ │ │ -89E49 PAYLOAD │ │ │ │ - │ │ │ │ -8B9D3 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -8B9D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8B9D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8B9D9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8B9DB Compression Method 0008 (8) 'Deflated' │ │ │ │ -8B9DD Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -8B9E1 CRC 6A8C22D8 (1787568856) │ │ │ │ -8B9E5 Compressed Size 000016FB (5883) │ │ │ │ -8B9E9 Uncompressed Size 00008B10 (35600) │ │ │ │ -8B9ED Filename Length 0012 (18) │ │ │ │ -8B9EF Extra Length 001C (28) │ │ │ │ -8B9F1 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8B9F1: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8BA03 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8BA05 Length 0009 (9) │ │ │ │ -8BA07 Flags 03 (3) 'Modification Access' │ │ │ │ -8BA08 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8BA0C Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8BA10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8BA12 Length 000B (11) │ │ │ │ -8BA14 Version 01 (1) │ │ │ │ -8BA15 UID Size 04 (4) │ │ │ │ -8BA16 UID 00000000 (0) │ │ │ │ -8BA1A GID Size 04 (4) │ │ │ │ -8BA1B GID 00000000 (0) │ │ │ │ -8BA1F PAYLOAD │ │ │ │ - │ │ │ │ -8D11A LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -8D11E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8D11F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8D120 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8D122 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8D124 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -8D128 CRC 627017A3 (1651513251) │ │ │ │ -8D12C Compressed Size 00001E0A (7690) │ │ │ │ -8D130 Uncompressed Size 00008801 (34817) │ │ │ │ -8D134 Filename Length 0016 (22) │ │ │ │ -8D136 Extra Length 001C (28) │ │ │ │ -8D138 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8D138: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8D14E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8D150 Length 0009 (9) │ │ │ │ -8D152 Flags 03 (3) 'Modification Access' │ │ │ │ -8D153 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8D157 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8D15B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8D15D Length 000B (11) │ │ │ │ -8D15F Version 01 (1) │ │ │ │ -8D160 UID Size 04 (4) │ │ │ │ -8D161 UID 00000000 (0) │ │ │ │ -8D165 GID Size 04 (4) │ │ │ │ -8D166 GID 00000000 (0) │ │ │ │ -8D16A PAYLOAD │ │ │ │ - │ │ │ │ -8EF74 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -8EF78 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8EF79 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8EF7A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8EF7C Compression Method 0008 (8) 'Deflated' │ │ │ │ -8EF7E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -8EF82 CRC 47B38AFD (1202948861) │ │ │ │ -8EF86 Compressed Size 000029A8 (10664) │ │ │ │ -8EF8A Uncompressed Size 0000D04D (53325) │ │ │ │ -8EF8E Filename Length 001A (26) │ │ │ │ -8EF90 Extra Length 001C (28) │ │ │ │ -8EF92 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8EF92: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8EFAC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8EFAE Length 0009 (9) │ │ │ │ -8EFB0 Flags 03 (3) 'Modification Access' │ │ │ │ -8EFB1 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8EFB5 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -8EFB9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8EFBB Length 000B (11) │ │ │ │ -8EFBD Version 01 (1) │ │ │ │ -8EFBE UID Size 04 (4) │ │ │ │ -8EFBF UID 00000000 (0) │ │ │ │ -8EFC3 GID Size 04 (4) │ │ │ │ -8EFC4 GID 00000000 (0) │ │ │ │ -8EFC8 PAYLOAD │ │ │ │ - │ │ │ │ -91970 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -91974 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -91975 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -91976 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -91978 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9197A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -9197E CRC 0141A5BD (21079485) │ │ │ │ -91982 Compressed Size 000009A9 (2473) │ │ │ │ -91986 Uncompressed Size 00001DB4 (7604) │ │ │ │ -9198A Filename Length 0018 (24) │ │ │ │ -9198C Extra Length 001C (28) │ │ │ │ -9198E Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9198E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -919A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -919A8 Length 0009 (9) │ │ │ │ -919AA Flags 03 (3) 'Modification Access' │ │ │ │ -919AB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -919AF Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -919B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -919B5 Length 000B (11) │ │ │ │ -919B7 Version 01 (1) │ │ │ │ -919B8 UID Size 04 (4) │ │ │ │ -919B9 UID 00000000 (0) │ │ │ │ -919BD GID Size 04 (4) │ │ │ │ -919BE GID 00000000 (0) │ │ │ │ -919C2 PAYLOAD │ │ │ │ - │ │ │ │ -9236B LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -9236F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -92370 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -92371 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -92373 Compression Method 0008 (8) 'Deflated' │ │ │ │ -92375 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -92379 CRC F0556E9A (4032130714) │ │ │ │ -9237D Compressed Size 000152EE (86766) │ │ │ │ -92381 Uncompressed Size 000159F8 (88568) │ │ │ │ -92385 Filename Length 001E (30) │ │ │ │ -92387 Extra Length 001C (28) │ │ │ │ -92389 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x92389: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -923A7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -923A9 Length 0009 (9) │ │ │ │ -923AB Flags 03 (3) 'Modification Access' │ │ │ │ -923AC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -923B0 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -923B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -923B6 Length 000B (11) │ │ │ │ -923B8 Version 01 (1) │ │ │ │ -923B9 UID Size 04 (4) │ │ │ │ -923BA UID 00000000 (0) │ │ │ │ -923BE GID Size 04 (4) │ │ │ │ -923BF GID 00000000 (0) │ │ │ │ -923C3 PAYLOAD │ │ │ │ - │ │ │ │ -A76B1 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -A76B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A76B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A76B7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A76B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A76BB Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -A76BF CRC F5E2129F (4125233823) │ │ │ │ -A76C3 Compressed Size 000016BC (5820) │ │ │ │ -A76C7 Uncompressed Size 000016CD (5837) │ │ │ │ -A76CB Filename Length 0015 (21) │ │ │ │ -A76CD Extra Length 001C (28) │ │ │ │ -A76CF Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA76CF: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A76E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A76E6 Length 0009 (9) │ │ │ │ -A76E8 Flags 03 (3) 'Modification Access' │ │ │ │ -A76E9 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -A76ED Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -A76F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A76F3 Length 000B (11) │ │ │ │ -A76F5 Version 01 (1) │ │ │ │ -A76F6 UID Size 04 (4) │ │ │ │ -A76F7 UID 00000000 (0) │ │ │ │ -A76FB GID Size 04 (4) │ │ │ │ -A76FC GID 00000000 (0) │ │ │ │ -A7700 PAYLOAD │ │ │ │ - │ │ │ │ -A8DBC LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -A8DC0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A8DC1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A8DC2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A8DC4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A8DC6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -A8DCA CRC F5E2129F (4125233823) │ │ │ │ -A8DCE Compressed Size 000016BC (5820) │ │ │ │ -A8DD2 Uncompressed Size 000016CD (5837) │ │ │ │ -A8DD6 Filename Length 001C (28) │ │ │ │ -A8DD8 Extra Length 001C (28) │ │ │ │ -A8DDA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA8DDA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A8DF6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A8DF8 Length 0009 (9) │ │ │ │ -A8DFA Flags 03 (3) 'Modification Access' │ │ │ │ -A8DFB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -A8DFF Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -A8E03 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A8E05 Length 000B (11) │ │ │ │ -A8E07 Version 01 (1) │ │ │ │ -A8E08 UID Size 04 (4) │ │ │ │ -A8E09 UID 00000000 (0) │ │ │ │ -A8E0D GID Size 04 (4) │ │ │ │ -A8E0E GID 00000000 (0) │ │ │ │ -A8E12 PAYLOAD │ │ │ │ - │ │ │ │ -AA4CE LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -AA4D2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AA4D3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AA4D4 General Purpose Flag 0000 (0) │ │ │ │ -AA4D6 Compression Method 0000 (0) 'Stored' │ │ │ │ -AA4D8 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -AA4DC CRC FC95F24B (4237685323) │ │ │ │ -AA4E0 Compressed Size 00001B84 (7044) │ │ │ │ -AA4E4 Uncompressed Size 00001B84 (7044) │ │ │ │ -AA4E8 Filename Length 0016 (22) │ │ │ │ -AA4EA Extra Length 001C (28) │ │ │ │ -AA4EC Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAA4EC: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AA502 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AA504 Length 0009 (9) │ │ │ │ -AA506 Flags 03 (3) 'Modification Access' │ │ │ │ -AA507 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AA50B Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AA50F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AA511 Length 000B (11) │ │ │ │ -AA513 Version 01 (1) │ │ │ │ -AA514 UID Size 04 (4) │ │ │ │ -AA515 UID 00000000 (0) │ │ │ │ -AA519 GID Size 04 (4) │ │ │ │ -AA51A GID 00000000 (0) │ │ │ │ -AA51E PAYLOAD │ │ │ │ - │ │ │ │ -AC0A2 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -AC0A6 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AC0A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AC0A8 General Purpose Flag 0000 (0) │ │ │ │ -AC0AA Compression Method 0000 (0) 'Stored' │ │ │ │ -AC0AC Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -AC0B0 CRC D0D71F86 (3503759238) │ │ │ │ -AC0B4 Compressed Size 00000B7B (2939) │ │ │ │ -AC0B8 Uncompressed Size 00000B7B (2939) │ │ │ │ -AC0BC Filename Length 0016 (22) │ │ │ │ -AC0BE Extra Length 001C (28) │ │ │ │ -AC0C0 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAC0C0: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AC0D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AC0D8 Length 0009 (9) │ │ │ │ -AC0DA Flags 03 (3) 'Modification Access' │ │ │ │ -AC0DB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AC0DF Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AC0E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AC0E5 Length 000B (11) │ │ │ │ -AC0E7 Version 01 (1) │ │ │ │ -AC0E8 UID Size 04 (4) │ │ │ │ -AC0E9 UID 00000000 (0) │ │ │ │ -AC0ED GID Size 04 (4) │ │ │ │ -AC0EE GID 00000000 (0) │ │ │ │ -AC0F2 PAYLOAD │ │ │ │ - │ │ │ │ -ACC6D LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -ACC71 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -ACC72 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -ACC73 General Purpose Flag 0000 (0) │ │ │ │ -ACC75 Compression Method 0000 (0) 'Stored' │ │ │ │ -ACC77 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -ACC7B CRC FFF9C4D2 (4294558930) │ │ │ │ -ACC7F Compressed Size 0000138F (5007) │ │ │ │ -ACC83 Uncompressed Size 0000138F (5007) │ │ │ │ -ACC87 Filename Length 0016 (22) │ │ │ │ -ACC89 Extra Length 001C (28) │ │ │ │ -ACC8B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xACC8B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -ACCA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -ACCA3 Length 0009 (9) │ │ │ │ -ACCA5 Flags 03 (3) 'Modification Access' │ │ │ │ -ACCA6 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -ACCAA Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -ACCAE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -ACCB0 Length 000B (11) │ │ │ │ -ACCB2 Version 01 (1) │ │ │ │ -ACCB3 UID Size 04 (4) │ │ │ │ -ACCB4 UID 00000000 (0) │ │ │ │ -ACCB8 GID Size 04 (4) │ │ │ │ -ACCB9 GID 00000000 (0) │ │ │ │ -ACCBD PAYLOAD │ │ │ │ - │ │ │ │ -AE04C LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -AE050 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AE051 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AE052 General Purpose Flag 0000 (0) │ │ │ │ -AE054 Compression Method 0000 (0) 'Stored' │ │ │ │ -AE056 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -AE05A CRC A1037E8E (2701360782) │ │ │ │ -AE05E Compressed Size 0000145E (5214) │ │ │ │ -AE062 Uncompressed Size 0000145E (5214) │ │ │ │ -AE066 Filename Length 0016 (22) │ │ │ │ -AE068 Extra Length 001C (28) │ │ │ │ -AE06A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAE06A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AE080 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AE082 Length 0009 (9) │ │ │ │ -AE084 Flags 03 (3) 'Modification Access' │ │ │ │ -AE085 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AE089 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AE08D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AE08F Length 000B (11) │ │ │ │ -AE091 Version 01 (1) │ │ │ │ -AE092 UID Size 04 (4) │ │ │ │ -AE093 UID 00000000 (0) │ │ │ │ -AE097 GID Size 04 (4) │ │ │ │ -AE098 GID 00000000 (0) │ │ │ │ -AE09C PAYLOAD │ │ │ │ - │ │ │ │ -AF4FA LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -AF4FE Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AF4FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AF500 General Purpose Flag 0000 (0) │ │ │ │ -AF502 Compression Method 0000 (0) 'Stored' │ │ │ │ -AF504 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -AF508 CRC 5E9E64F1 (1587438833) │ │ │ │ -AF50C Compressed Size 000008EC (2284) │ │ │ │ -AF510 Uncompressed Size 000008EC (2284) │ │ │ │ -AF514 Filename Length 0016 (22) │ │ │ │ -AF516 Extra Length 001C (28) │ │ │ │ -AF518 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAF518: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AF52E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AF530 Length 0009 (9) │ │ │ │ -AF532 Flags 03 (3) 'Modification Access' │ │ │ │ -AF533 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AF537 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AF53B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AF53D Length 000B (11) │ │ │ │ -AF53F Version 01 (1) │ │ │ │ -AF540 UID Size 04 (4) │ │ │ │ -AF541 UID 00000000 (0) │ │ │ │ -AF545 GID Size 04 (4) │ │ │ │ -AF546 GID 00000000 (0) │ │ │ │ -AF54A PAYLOAD │ │ │ │ - │ │ │ │ -AFE36 LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ -AFE3A Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AFE3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AFE3C General Purpose Flag 0000 (0) │ │ │ │ -AFE3E Compression Method 0000 (0) 'Stored' │ │ │ │ -AFE40 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -AFE44 CRC 42E340AB (1122189483) │ │ │ │ -AFE48 Compressed Size 00001F2E (7982) │ │ │ │ -AFE4C Uncompressed Size 00001F2E (7982) │ │ │ │ -AFE50 Filename Length 001E (30) │ │ │ │ -AFE52 Extra Length 001C (28) │ │ │ │ -AFE54 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAFE54: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AFE72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AFE74 Length 0009 (9) │ │ │ │ -AFE76 Flags 03 (3) 'Modification Access' │ │ │ │ -AFE77 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AFE7B Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -AFE7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AFE81 Length 000B (11) │ │ │ │ -AFE83 Version 01 (1) │ │ │ │ -AFE84 UID Size 04 (4) │ │ │ │ -AFE85 UID 00000000 (0) │ │ │ │ -AFE89 GID Size 04 (4) │ │ │ │ -AFE8A GID 00000000 (0) │ │ │ │ -AFE8E PAYLOAD │ │ │ │ - │ │ │ │ -B1DBC LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ -B1DC0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B1DC1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B1DC2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B1DC4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B1DC6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B1DCA CRC 93BCFC9A (2478636186) │ │ │ │ -B1DCE Compressed Size 00003D67 (15719) │ │ │ │ -B1DD2 Uncompressed Size 0001664D (91725) │ │ │ │ -B1DD6 Filename Length 001A (26) │ │ │ │ -B1DD8 Extra Length 001C (28) │ │ │ │ -B1DDA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB1DDA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B1DF4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B1DF6 Length 0009 (9) │ │ │ │ -B1DF8 Flags 03 (3) 'Modification Access' │ │ │ │ -B1DF9 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B1DFD Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B1E01 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B1E03 Length 000B (11) │ │ │ │ -B1E05 Version 01 (1) │ │ │ │ -B1E06 UID Size 04 (4) │ │ │ │ -B1E07 UID 00000000 (0) │ │ │ │ -B1E0B GID Size 04 (4) │ │ │ │ -B1E0C GID 00000000 (0) │ │ │ │ -B1E10 PAYLOAD │ │ │ │ - │ │ │ │ -B5B77 LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ -B5B7B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B5B7C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B5B7D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B5B7F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B5B81 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B5B85 CRC 3A3194F5 (976327925) │ │ │ │ -B5B89 Compressed Size 000029D3 (10707) │ │ │ │ -B5B8D Uncompressed Size 0000BB37 (47927) │ │ │ │ -B5B91 Filename Length 0018 (24) │ │ │ │ -B5B93 Extra Length 001C (28) │ │ │ │ -B5B95 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB5B95: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B5BAD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B5BAF Length 0009 (9) │ │ │ │ -B5BB1 Flags 03 (3) 'Modification Access' │ │ │ │ -B5BB2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B5BB6 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B5BBA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B5BBC Length 000B (11) │ │ │ │ -B5BBE Version 01 (1) │ │ │ │ -B5BBF UID Size 04 (4) │ │ │ │ -B5BC0 UID 00000000 (0) │ │ │ │ -B5BC4 GID Size 04 (4) │ │ │ │ -B5BC5 GID 00000000 (0) │ │ │ │ -B5BC9 PAYLOAD │ │ │ │ - │ │ │ │ -B859C LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ -B85A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B85A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B85A2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B85A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B85A6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B85AA CRC DCB3B516 (3702764822) │ │ │ │ -B85AE Compressed Size 000000AE (174) │ │ │ │ -B85B2 Uncompressed Size 000000FC (252) │ │ │ │ -B85B6 Filename Length 0016 (22) │ │ │ │ -B85B8 Extra Length 001C (28) │ │ │ │ -B85BA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB85BA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B85D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B85D2 Length 0009 (9) │ │ │ │ -B85D4 Flags 03 (3) 'Modification Access' │ │ │ │ -B85D5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B85D9 Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B85DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B85DF Length 000B (11) │ │ │ │ -B85E1 Version 01 (1) │ │ │ │ -B85E2 UID Size 04 (4) │ │ │ │ -B85E3 UID 00000000 (0) │ │ │ │ -B85E7 GID Size 04 (4) │ │ │ │ -B85E8 GID 00000000 (0) │ │ │ │ -B85EC PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +2D599 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +2D59D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2D59E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2D59F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2D5A1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2D5A3 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2D5A7 CRC 9B224725 (2602714917) │ │ │ │ +2D5AB Compressed Size 0000069E (1694) │ │ │ │ +2D5AF Uncompressed Size 000011F2 (4594) │ │ │ │ +2D5B3 Filename Length 001C (28) │ │ │ │ +2D5B5 Extra Length 001C (28) │ │ │ │ +2D5B7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2D5B7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2D5D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2D5D5 Length 0009 (9) │ │ │ │ +2D5D7 Flags 03 (3) 'Modification Access' │ │ │ │ +2D5D8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2D5DC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2D5E0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2D5E2 Length 000B (11) │ │ │ │ +2D5E4 Version 01 (1) │ │ │ │ +2D5E5 UID Size 04 (4) │ │ │ │ +2D5E6 UID 00000000 (0) │ │ │ │ +2D5EA GID Size 04 (4) │ │ │ │ +2D5EB GID 00000000 (0) │ │ │ │ +2D5EF PAYLOAD │ │ │ │ + │ │ │ │ +2DC8D LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +2DC91 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2DC92 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2DC93 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2DC95 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2DC97 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2DC9B CRC 6671B663 (1718728291) │ │ │ │ +2DC9F Compressed Size 0000107A (4218) │ │ │ │ +2DCA3 Uncompressed Size 00004BFD (19453) │ │ │ │ +2DCA7 Filename Length 001B (27) │ │ │ │ +2DCA9 Extra Length 001C (28) │ │ │ │ +2DCAB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2DCAB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2DCC6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2DCC8 Length 0009 (9) │ │ │ │ +2DCCA Flags 03 (3) 'Modification Access' │ │ │ │ +2DCCB Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2DCCF Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2DCD3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2DCD5 Length 000B (11) │ │ │ │ +2DCD7 Version 01 (1) │ │ │ │ +2DCD8 UID Size 04 (4) │ │ │ │ +2DCD9 UID 00000000 (0) │ │ │ │ +2DCDD GID Size 04 (4) │ │ │ │ +2DCDE GID 00000000 (0) │ │ │ │ +2DCE2 PAYLOAD │ │ │ │ + │ │ │ │ +2ED5C LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +2ED60 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2ED61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2ED62 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2ED64 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2ED66 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2ED6A CRC 9C84C038 (2625945656) │ │ │ │ +2ED6E Compressed Size 00003BE0 (15328) │ │ │ │ +2ED72 Uncompressed Size 0000D781 (55169) │ │ │ │ +2ED76 Filename Length 001D (29) │ │ │ │ +2ED78 Extra Length 001C (28) │ │ │ │ +2ED7A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2ED7A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2ED97 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2ED99 Length 0009 (9) │ │ │ │ +2ED9B Flags 03 (3) 'Modification Access' │ │ │ │ +2ED9C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2EDA0 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +2EDA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2EDA6 Length 000B (11) │ │ │ │ +2EDA8 Version 01 (1) │ │ │ │ +2EDA9 UID Size 04 (4) │ │ │ │ +2EDAA UID 00000000 (0) │ │ │ │ +2EDAE GID Size 04 (4) │ │ │ │ +2EDAF GID 00000000 (0) │ │ │ │ +2EDB3 PAYLOAD │ │ │ │ + │ │ │ │ +32993 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +32997 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32998 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32999 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3299B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3299D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +329A1 CRC 7CC10163 (2093023587) │ │ │ │ +329A5 Compressed Size 00000D66 (3430) │ │ │ │ +329A9 Uncompressed Size 0000388B (14475) │ │ │ │ +329AD Filename Length 001D (29) │ │ │ │ +329AF Extra Length 001C (28) │ │ │ │ +329B1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x329B1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +329CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +329D0 Length 0009 (9) │ │ │ │ +329D2 Flags 03 (3) 'Modification Access' │ │ │ │ +329D3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +329D7 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +329DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +329DD Length 000B (11) │ │ │ │ +329DF Version 01 (1) │ │ │ │ +329E0 UID Size 04 (4) │ │ │ │ +329E1 UID 00000000 (0) │ │ │ │ +329E5 GID Size 04 (4) │ │ │ │ +329E6 GID 00000000 (0) │ │ │ │ +329EA PAYLOAD │ │ │ │ + │ │ │ │ +33750 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +33754 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +33755 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +33756 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +33758 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3375A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3375E CRC D13EB515 (3510547733) │ │ │ │ +33762 Compressed Size 00001C5F (7263) │ │ │ │ +33766 Uncompressed Size 0000C184 (49540) │ │ │ │ +3376A Filename Length 001A (26) │ │ │ │ +3376C Extra Length 001C (28) │ │ │ │ +3376E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3376E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +33788 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3378A Length 0009 (9) │ │ │ │ +3378C Flags 03 (3) 'Modification Access' │ │ │ │ +3378D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +33791 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +33795 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +33797 Length 000B (11) │ │ │ │ +33799 Version 01 (1) │ │ │ │ +3379A UID Size 04 (4) │ │ │ │ +3379B UID 00000000 (0) │ │ │ │ +3379F GID Size 04 (4) │ │ │ │ +337A0 GID 00000000 (0) │ │ │ │ +337A4 PAYLOAD │ │ │ │ + │ │ │ │ +35403 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +35407 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35408 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35409 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3540B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3540D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35411 CRC 2BF4762B (737441323) │ │ │ │ +35415 Compressed Size 000003DE (990) │ │ │ │ +35419 Uncompressed Size 00000933 (2355) │ │ │ │ +3541D Filename Length 0012 (18) │ │ │ │ +3541F Extra Length 001C (28) │ │ │ │ +35421 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35421: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35433 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35435 Length 0009 (9) │ │ │ │ +35437 Flags 03 (3) 'Modification Access' │ │ │ │ +35438 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3543C Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35440 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35442 Length 000B (11) │ │ │ │ +35444 Version 01 (1) │ │ │ │ +35445 UID Size 04 (4) │ │ │ │ +35446 UID 00000000 (0) │ │ │ │ +3544A GID Size 04 (4) │ │ │ │ +3544B GID 00000000 (0) │ │ │ │ +3544F PAYLOAD │ │ │ │ + │ │ │ │ +3582D LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +35831 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35832 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35833 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35835 Compression Method 0008 (8) 'Deflated' │ │ │ │ +35837 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3583B CRC A4B161AE (2763088302) │ │ │ │ +3583F Compressed Size 000001D1 (465) │ │ │ │ +35843 Uncompressed Size 0000030F (783) │ │ │ │ +35847 Filename Length 0020 (32) │ │ │ │ +35849 Extra Length 001C (28) │ │ │ │ +3584B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3584B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3586B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3586D Length 0009 (9) │ │ │ │ +3586F Flags 03 (3) 'Modification Access' │ │ │ │ +35870 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35874 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35878 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3587A Length 000B (11) │ │ │ │ +3587C Version 01 (1) │ │ │ │ +3587D UID Size 04 (4) │ │ │ │ +3587E UID 00000000 (0) │ │ │ │ +35882 GID Size 04 (4) │ │ │ │ +35883 GID 00000000 (0) │ │ │ │ +35887 PAYLOAD │ │ │ │ + │ │ │ │ +35A58 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +35A5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35A5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35A5E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35A60 Compression Method 0008 (8) 'Deflated' │ │ │ │ +35A62 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35A66 CRC 075D22ED (123544301) │ │ │ │ +35A6A Compressed Size 000017A7 (6055) │ │ │ │ +35A6E Uncompressed Size 00009D16 (40214) │ │ │ │ +35A72 Filename Length 001B (27) │ │ │ │ +35A74 Extra Length 001C (28) │ │ │ │ +35A76 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35A76: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35A91 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35A93 Length 0009 (9) │ │ │ │ +35A95 Flags 03 (3) 'Modification Access' │ │ │ │ +35A96 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35A9A Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +35A9E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35AA0 Length 000B (11) │ │ │ │ +35AA2 Version 01 (1) │ │ │ │ +35AA3 UID Size 04 (4) │ │ │ │ +35AA4 UID 00000000 (0) │ │ │ │ +35AA8 GID Size 04 (4) │ │ │ │ +35AA9 GID 00000000 (0) │ │ │ │ +35AAD PAYLOAD │ │ │ │ + │ │ │ │ +37254 LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +37258 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +37259 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3725A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3725C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3725E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +37262 CRC 1F5C05CF (526124495) │ │ │ │ +37266 Compressed Size 00001370 (4976) │ │ │ │ +3726A Uncompressed Size 00003B5F (15199) │ │ │ │ +3726E Filename Length 0015 (21) │ │ │ │ +37270 Extra Length 001C (28) │ │ │ │ +37272 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x37272: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +37287 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +37289 Length 0009 (9) │ │ │ │ +3728B Flags 03 (3) 'Modification Access' │ │ │ │ +3728C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +37290 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +37294 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +37296 Length 000B (11) │ │ │ │ +37298 Version 01 (1) │ │ │ │ +37299 UID Size 04 (4) │ │ │ │ +3729A UID 00000000 (0) │ │ │ │ +3729E GID Size 04 (4) │ │ │ │ +3729F GID 00000000 (0) │ │ │ │ +372A3 PAYLOAD │ │ │ │ + │ │ │ │ +38613 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +38617 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38618 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38619 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3861B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3861D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +38621 CRC ED1A23A7 (3977913255) │ │ │ │ +38625 Compressed Size 00000ACC (2764) │ │ │ │ +38629 Uncompressed Size 00002133 (8499) │ │ │ │ +3862D Filename Length 0011 (17) │ │ │ │ +3862F Extra Length 001C (28) │ │ │ │ +38631 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x38631: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +38642 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +38644 Length 0009 (9) │ │ │ │ +38646 Flags 03 (3) 'Modification Access' │ │ │ │ +38647 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3864B Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3864F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +38651 Length 000B (11) │ │ │ │ +38653 Version 01 (1) │ │ │ │ +38654 UID Size 04 (4) │ │ │ │ +38655 UID 00000000 (0) │ │ │ │ +38659 GID Size 04 (4) │ │ │ │ +3865A GID 00000000 (0) │ │ │ │ +3865E PAYLOAD │ │ │ │ + │ │ │ │ +3912A LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +3912E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3912F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +39130 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +39132 Compression Method 0008 (8) 'Deflated' │ │ │ │ +39134 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +39138 CRC AFD8454F (2950186319) │ │ │ │ +3913C Compressed Size 000003FC (1020) │ │ │ │ +39140 Uncompressed Size 00000F0A (3850) │ │ │ │ +39144 Filename Length 0014 (20) │ │ │ │ +39146 Extra Length 001C (28) │ │ │ │ +39148 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x39148: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3915C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3915E Length 0009 (9) │ │ │ │ +39160 Flags 03 (3) 'Modification Access' │ │ │ │ +39161 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +39165 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +39169 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3916B Length 000B (11) │ │ │ │ +3916D Version 01 (1) │ │ │ │ +3916E UID Size 04 (4) │ │ │ │ +3916F UID 00000000 (0) │ │ │ │ +39173 GID Size 04 (4) │ │ │ │ +39174 GID 00000000 (0) │ │ │ │ +39178 PAYLOAD │ │ │ │ + │ │ │ │ +39574 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ +39578 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +39579 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3957A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3957C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3957E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +39582 CRC A1F8FAEC (2717448940) │ │ │ │ +39586 Compressed Size 0000125F (4703) │ │ │ │ +3958A Uncompressed Size 00003467 (13415) │ │ │ │ +3958E Filename Length 0014 (20) │ │ │ │ +39590 Extra Length 001C (28) │ │ │ │ +39592 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x39592: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +395A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +395A8 Length 0009 (9) │ │ │ │ +395AA Flags 03 (3) 'Modification Access' │ │ │ │ +395AB Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +395AF Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +395B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +395B5 Length 000B (11) │ │ │ │ +395B7 Version 01 (1) │ │ │ │ +395B8 UID Size 04 (4) │ │ │ │ +395B9 UID 00000000 (0) │ │ │ │ +395BD GID Size 04 (4) │ │ │ │ +395BE GID 00000000 (0) │ │ │ │ +395C2 PAYLOAD │ │ │ │ + │ │ │ │ +3A821 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +3A825 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3A826 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3A827 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3A829 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3A82B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3A82F CRC F8562843 (4166395971) │ │ │ │ +3A833 Compressed Size 00000ACA (2762) │ │ │ │ +3A837 Uncompressed Size 000022FD (8957) │ │ │ │ +3A83B Filename Length 001B (27) │ │ │ │ +3A83D Extra Length 001C (28) │ │ │ │ +3A83F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3A83F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3A85A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3A85C Length 0009 (9) │ │ │ │ +3A85E Flags 03 (3) 'Modification Access' │ │ │ │ +3A85F Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3A863 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3A867 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3A869 Length 000B (11) │ │ │ │ +3A86B Version 01 (1) │ │ │ │ +3A86C UID Size 04 (4) │ │ │ │ +3A86D UID 00000000 (0) │ │ │ │ +3A871 GID Size 04 (4) │ │ │ │ +3A872 GID 00000000 (0) │ │ │ │ +3A876 PAYLOAD │ │ │ │ + │ │ │ │ +3B340 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ +3B344 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3B345 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3B346 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3B348 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3B34A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3B34E CRC A9BABEAA (2847588010) │ │ │ │ +3B352 Compressed Size 00000C4D (3149) │ │ │ │ +3B356 Uncompressed Size 0000273A (10042) │ │ │ │ +3B35A Filename Length 0013 (19) │ │ │ │ +3B35C Extra Length 001C (28) │ │ │ │ +3B35E Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3B35E: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3B371 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3B373 Length 0009 (9) │ │ │ │ +3B375 Flags 03 (3) 'Modification Access' │ │ │ │ +3B376 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3B37A Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3B37E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3B380 Length 000B (11) │ │ │ │ +3B382 Version 01 (1) │ │ │ │ +3B383 UID Size 04 (4) │ │ │ │ +3B384 UID 00000000 (0) │ │ │ │ +3B388 GID Size 04 (4) │ │ │ │ +3B389 GID 00000000 (0) │ │ │ │ +3B38D PAYLOAD │ │ │ │ + │ │ │ │ +3BFDA LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ +3BFDE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3BFDF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3BFE0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3BFE2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3BFE4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3BFE8 CRC 90533DAF (2421374383) │ │ │ │ +3BFEC Compressed Size 00000C8F (3215) │ │ │ │ +3BFF0 Uncompressed Size 00003D0E (15630) │ │ │ │ +3BFF4 Filename Length 0014 (20) │ │ │ │ +3BFF6 Extra Length 001C (28) │ │ │ │ +3BFF8 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3BFF8: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C00C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C00E Length 0009 (9) │ │ │ │ +3C010 Flags 03 (3) 'Modification Access' │ │ │ │ +3C011 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3C015 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3C019 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C01B Length 000B (11) │ │ │ │ +3C01D Version 01 (1) │ │ │ │ +3C01E UID Size 04 (4) │ │ │ │ +3C01F UID 00000000 (0) │ │ │ │ +3C023 GID Size 04 (4) │ │ │ │ +3C024 GID 00000000 (0) │ │ │ │ +3C028 PAYLOAD │ │ │ │ + │ │ │ │ +3CCB7 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +3CCBB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3CCBC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3CCBD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3CCBF Compression Method 0008 (8) 'Deflated' │ │ │ │ +3CCC1 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3CCC5 CRC C54076B2 (3309336242) │ │ │ │ +3CCC9 Compressed Size 00000F44 (3908) │ │ │ │ +3CCCD Uncompressed Size 00003742 (14146) │ │ │ │ +3CCD1 Filename Length 000F (15) │ │ │ │ +3CCD3 Extra Length 001C (28) │ │ │ │ +3CCD5 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3CCD5: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3CCE4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3CCE6 Length 0009 (9) │ │ │ │ +3CCE8 Flags 03 (3) 'Modification Access' │ │ │ │ +3CCE9 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3CCED Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3CCF1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3CCF3 Length 000B (11) │ │ │ │ +3CCF5 Version 01 (1) │ │ │ │ +3CCF6 UID Size 04 (4) │ │ │ │ +3CCF7 UID 00000000 (0) │ │ │ │ +3CCFB GID Size 04 (4) │ │ │ │ +3CCFC GID 00000000 (0) │ │ │ │ +3CD00 PAYLOAD │ │ │ │ + │ │ │ │ +3DC44 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +3DC48 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3DC49 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3DC4A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3DC4C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3DC4E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3DC52 CRC 5282A93D (1384294717) │ │ │ │ +3DC56 Compressed Size 000006B9 (1721) │ │ │ │ +3DC5A Uncompressed Size 00001A77 (6775) │ │ │ │ +3DC5E Filename Length 000F (15) │ │ │ │ +3DC60 Extra Length 001C (28) │ │ │ │ +3DC62 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3DC62: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3DC71 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3DC73 Length 0009 (9) │ │ │ │ +3DC75 Flags 03 (3) 'Modification Access' │ │ │ │ +3DC76 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3DC7A Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3DC7E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3DC80 Length 000B (11) │ │ │ │ +3DC82 Version 01 (1) │ │ │ │ +3DC83 UID Size 04 (4) │ │ │ │ +3DC84 UID 00000000 (0) │ │ │ │ +3DC88 GID Size 04 (4) │ │ │ │ +3DC89 GID 00000000 (0) │ │ │ │ +3DC8D PAYLOAD │ │ │ │ + │ │ │ │ +3E346 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +3E34A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3E34B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3E34C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3E34E Compression Method 0008 (8) 'Deflated' │ │ │ │ +3E350 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3E354 CRC FD7C9D36 (4252802358) │ │ │ │ +3E358 Compressed Size 00001A42 (6722) │ │ │ │ +3E35C Uncompressed Size 000064F8 (25848) │ │ │ │ +3E360 Filename Length 0013 (19) │ │ │ │ +3E362 Extra Length 001C (28) │ │ │ │ +3E364 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3E364: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3E377 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3E379 Length 0009 (9) │ │ │ │ +3E37B Flags 03 (3) 'Modification Access' │ │ │ │ +3E37C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3E380 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3E384 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3E386 Length 000B (11) │ │ │ │ +3E388 Version 01 (1) │ │ │ │ +3E389 UID Size 04 (4) │ │ │ │ +3E38A UID 00000000 (0) │ │ │ │ +3E38E GID Size 04 (4) │ │ │ │ +3E38F GID 00000000 (0) │ │ │ │ +3E393 PAYLOAD │ │ │ │ + │ │ │ │ +3FDD5 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +3FDD9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3FDDA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3FDDB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3FDDD Compression Method 0008 (8) 'Deflated' │ │ │ │ +3FDDF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3FDE3 CRC 5D9A5F08 (1570397960) │ │ │ │ +3FDE7 Compressed Size 000009A4 (2468) │ │ │ │ +3FDEB Uncompressed Size 00001B62 (7010) │ │ │ │ +3FDEF Filename Length 0010 (16) │ │ │ │ +3FDF1 Extra Length 001C (28) │ │ │ │ +3FDF3 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3FDF3: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3FE03 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3FE05 Length 0009 (9) │ │ │ │ +3FE07 Flags 03 (3) 'Modification Access' │ │ │ │ +3FE08 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3FE0C Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +3FE10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3FE12 Length 000B (11) │ │ │ │ +3FE14 Version 01 (1) │ │ │ │ +3FE15 UID Size 04 (4) │ │ │ │ +3FE16 UID 00000000 (0) │ │ │ │ +3FE1A GID Size 04 (4) │ │ │ │ +3FE1B GID 00000000 (0) │ │ │ │ +3FE1F PAYLOAD │ │ │ │ + │ │ │ │ +407C3 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +407C7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +407C8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +407C9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +407CB Compression Method 0008 (8) 'Deflated' │ │ │ │ +407CD Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +407D1 CRC 2F07E577 (789046647) │ │ │ │ +407D5 Compressed Size 000006B5 (1717) │ │ │ │ +407D9 Uncompressed Size 00001563 (5475) │ │ │ │ +407DD Filename Length 0012 (18) │ │ │ │ +407DF Extra Length 001C (28) │ │ │ │ +407E1 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x407E1: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +407F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +407F5 Length 0009 (9) │ │ │ │ +407F7 Flags 03 (3) 'Modification Access' │ │ │ │ +407F8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +407FC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +40800 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40802 Length 000B (11) │ │ │ │ +40804 Version 01 (1) │ │ │ │ +40805 UID Size 04 (4) │ │ │ │ +40806 UID 00000000 (0) │ │ │ │ +4080A GID Size 04 (4) │ │ │ │ +4080B GID 00000000 (0) │ │ │ │ +4080F PAYLOAD │ │ │ │ + │ │ │ │ +40EC4 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +40EC8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +40EC9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +40ECA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +40ECC Compression Method 0008 (8) 'Deflated' │ │ │ │ +40ECE Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +40ED2 CRC F55AF488 (4116378760) │ │ │ │ +40ED6 Compressed Size 00002D67 (11623) │ │ │ │ +40EDA Uncompressed Size 0000D07C (53372) │ │ │ │ +40EDE Filename Length 0010 (16) │ │ │ │ +40EE0 Extra Length 001C (28) │ │ │ │ +40EE2 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x40EE2: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40EF2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +40EF4 Length 0009 (9) │ │ │ │ +40EF6 Flags 03 (3) 'Modification Access' │ │ │ │ +40EF7 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +40EFB Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +40EFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40F01 Length 000B (11) │ │ │ │ +40F03 Version 01 (1) │ │ │ │ +40F04 UID Size 04 (4) │ │ │ │ +40F05 UID 00000000 (0) │ │ │ │ +40F09 GID Size 04 (4) │ │ │ │ +40F0A GID 00000000 (0) │ │ │ │ +40F0E PAYLOAD │ │ │ │ + │ │ │ │ +43C75 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +43C79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +43C7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +43C7B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +43C7D Compression Method 0008 (8) 'Deflated' │ │ │ │ +43C7F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +43C83 CRC 992A52CF (2569687759) │ │ │ │ +43C87 Compressed Size 00001E87 (7815) │ │ │ │ +43C8B Uncompressed Size 00009AA8 (39592) │ │ │ │ +43C8F Filename Length 0012 (18) │ │ │ │ +43C91 Extra Length 001C (28) │ │ │ │ +43C93 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x43C93: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +43CA5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +43CA7 Length 0009 (9) │ │ │ │ +43CA9 Flags 03 (3) 'Modification Access' │ │ │ │ +43CAA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +43CAE Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +43CB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +43CB4 Length 000B (11) │ │ │ │ +43CB6 Version 01 (1) │ │ │ │ +43CB7 UID Size 04 (4) │ │ │ │ +43CB8 UID 00000000 (0) │ │ │ │ +43CBC GID Size 04 (4) │ │ │ │ +43CBD GID 00000000 (0) │ │ │ │ +43CC1 PAYLOAD │ │ │ │ + │ │ │ │ +45B48 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +45B4C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +45B4D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +45B4E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45B50 Compression Method 0008 (8) 'Deflated' │ │ │ │ +45B52 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +45B56 CRC 82702DF7 (2188389879) │ │ │ │ +45B5A Compressed Size 0000146E (5230) │ │ │ │ +45B5E Uncompressed Size 00007ACD (31437) │ │ │ │ +45B62 Filename Length 0018 (24) │ │ │ │ +45B64 Extra Length 001C (28) │ │ │ │ +45B66 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x45B66: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +45B7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45B80 Length 0009 (9) │ │ │ │ +45B82 Flags 03 (3) 'Modification Access' │ │ │ │ +45B83 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +45B87 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +45B8B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +45B8D Length 000B (11) │ │ │ │ +45B8F Version 01 (1) │ │ │ │ +45B90 UID Size 04 (4) │ │ │ │ +45B91 UID 00000000 (0) │ │ │ │ +45B95 GID Size 04 (4) │ │ │ │ +45B96 GID 00000000 (0) │ │ │ │ +45B9A PAYLOAD │ │ │ │ + │ │ │ │ +47008 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +4700C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4700D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4700E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +47010 Compression Method 0008 (8) 'Deflated' │ │ │ │ +47012 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +47016 CRC 347EA9D0 (880716240) │ │ │ │ +4701A Compressed Size 00002148 (8520) │ │ │ │ +4701E Uncompressed Size 0000CCBC (52412) │ │ │ │ +47022 Filename Length 001F (31) │ │ │ │ +47024 Extra Length 001C (28) │ │ │ │ +47026 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x47026: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +47045 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +47047 Length 0009 (9) │ │ │ │ +47049 Flags 03 (3) 'Modification Access' │ │ │ │ +4704A Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +4704E Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +47052 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +47054 Length 000B (11) │ │ │ │ +47056 Version 01 (1) │ │ │ │ +47057 UID Size 04 (4) │ │ │ │ +47058 UID 00000000 (0) │ │ │ │ +4705C GID Size 04 (4) │ │ │ │ +4705D GID 00000000 (0) │ │ │ │ +47061 PAYLOAD │ │ │ │ + │ │ │ │ +491A9 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +491AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +491AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +491AF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +491B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +491B3 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +491B7 CRC 80CE55AF (2161005999) │ │ │ │ +491BB Compressed Size 000003F5 (1013) │ │ │ │ +491BF Uncompressed Size 000008A1 (2209) │ │ │ │ +491C3 Filename Length 001E (30) │ │ │ │ +491C5 Extra Length 001C (28) │ │ │ │ +491C7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x491C7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +491E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +491E7 Length 0009 (9) │ │ │ │ +491E9 Flags 03 (3) 'Modification Access' │ │ │ │ +491EA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +491EE Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +491F2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +491F4 Length 000B (11) │ │ │ │ +491F6 Version 01 (1) │ │ │ │ +491F7 UID Size 04 (4) │ │ │ │ +491F8 UID 00000000 (0) │ │ │ │ +491FC GID Size 04 (4) │ │ │ │ +491FD GID 00000000 (0) │ │ │ │ +49201 PAYLOAD │ │ │ │ + │ │ │ │ +495F6 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +495FA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +495FB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +495FC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +495FE Compression Method 0008 (8) 'Deflated' │ │ │ │ +49600 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +49604 CRC DF319C26 (3744570406) │ │ │ │ +49608 Compressed Size 00004308 (17160) │ │ │ │ +4960C Uncompressed Size 0000DAC9 (56009) │ │ │ │ +49610 Filename Length 0013 (19) │ │ │ │ +49612 Extra Length 001C (28) │ │ │ │ +49614 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x49614: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +49627 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +49629 Length 0009 (9) │ │ │ │ +4962B Flags 03 (3) 'Modification Access' │ │ │ │ +4962C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +49630 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +49634 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +49636 Length 000B (11) │ │ │ │ +49638 Version 01 (1) │ │ │ │ +49639 UID Size 04 (4) │ │ │ │ +4963A UID 00000000 (0) │ │ │ │ +4963E GID Size 04 (4) │ │ │ │ +4963F GID 00000000 (0) │ │ │ │ +49643 PAYLOAD │ │ │ │ + │ │ │ │ +4D94B LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +4D94F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4D950 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4D951 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4D953 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4D955 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +4D959 CRC 8C04B533 (2349118771) │ │ │ │ +4D95D Compressed Size 000026C0 (9920) │ │ │ │ +4D961 Uncompressed Size 00006E43 (28227) │ │ │ │ +4D965 Filename Length 0019 (25) │ │ │ │ +4D967 Extra Length 001C (28) │ │ │ │ +4D969 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4D969: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4D982 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4D984 Length 0009 (9) │ │ │ │ +4D986 Flags 03 (3) 'Modification Access' │ │ │ │ +4D987 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +4D98B Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +4D98F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4D991 Length 000B (11) │ │ │ │ +4D993 Version 01 (1) │ │ │ │ +4D994 UID Size 04 (4) │ │ │ │ +4D995 UID 00000000 (0) │ │ │ │ +4D999 GID Size 04 (4) │ │ │ │ +4D99A GID 00000000 (0) │ │ │ │ +4D99E PAYLOAD │ │ │ │ + │ │ │ │ +5005E LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +50062 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +50063 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +50064 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +50066 Compression Method 0008 (8) 'Deflated' │ │ │ │ +50068 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5006C CRC C06E9EF2 (3228475122) │ │ │ │ +50070 Compressed Size 0000273D (10045) │ │ │ │ +50074 Uncompressed Size 00008B81 (35713) │ │ │ │ +50078 Filename Length 0019 (25) │ │ │ │ +5007A Extra Length 001C (28) │ │ │ │ +5007C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5007C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +50095 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +50097 Length 0009 (9) │ │ │ │ +50099 Flags 03 (3) 'Modification Access' │ │ │ │ +5009A Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5009E Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +500A2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +500A4 Length 000B (11) │ │ │ │ +500A6 Version 01 (1) │ │ │ │ +500A7 UID Size 04 (4) │ │ │ │ +500A8 UID 00000000 (0) │ │ │ │ +500AC GID Size 04 (4) │ │ │ │ +500AD GID 00000000 (0) │ │ │ │ +500B1 PAYLOAD │ │ │ │ + │ │ │ │ +527EE LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +527F2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +527F3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +527F4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +527F6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +527F8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +527FC CRC F07DFB90 (4034788240) │ │ │ │ +52800 Compressed Size 00000ECA (3786) │ │ │ │ +52804 Uncompressed Size 000053BD (21437) │ │ │ │ +52808 Filename Length 0021 (33) │ │ │ │ +5280A Extra Length 001C (28) │ │ │ │ +5280C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5280C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5282D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5282F Length 0009 (9) │ │ │ │ +52831 Flags 03 (3) 'Modification Access' │ │ │ │ +52832 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +52836 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5283A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5283C Length 000B (11) │ │ │ │ +5283E Version 01 (1) │ │ │ │ +5283F UID Size 04 (4) │ │ │ │ +52840 UID 00000000 (0) │ │ │ │ +52844 GID Size 04 (4) │ │ │ │ +52845 GID 00000000 (0) │ │ │ │ +52849 PAYLOAD │ │ │ │ + │ │ │ │ +53713 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +53717 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +53718 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +53719 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5371B Compression Method 0008 (8) 'Deflated' │ │ │ │ +5371D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53721 CRC 7E4C0584 (2118911364) │ │ │ │ +53725 Compressed Size 00000534 (1332) │ │ │ │ +53729 Uncompressed Size 00000C94 (3220) │ │ │ │ +5372D Filename Length 0017 (23) │ │ │ │ +5372F Extra Length 001C (28) │ │ │ │ +53731 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x53731: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +53748 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5374A Length 0009 (9) │ │ │ │ +5374C Flags 03 (3) 'Modification Access' │ │ │ │ +5374D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53751 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53755 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +53757 Length 000B (11) │ │ │ │ +53759 Version 01 (1) │ │ │ │ +5375A UID Size 04 (4) │ │ │ │ +5375B UID 00000000 (0) │ │ │ │ +5375F GID Size 04 (4) │ │ │ │ +53760 GID 00000000 (0) │ │ │ │ +53764 PAYLOAD │ │ │ │ + │ │ │ │ +53C98 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +53C9C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +53C9D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +53C9E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +53CA0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +53CA2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53CA6 CRC 76893EF6 (1988706038) │ │ │ │ +53CAA Compressed Size 00000465 (1125) │ │ │ │ +53CAE Uncompressed Size 0000092F (2351) │ │ │ │ +53CB2 Filename Length 001B (27) │ │ │ │ +53CB4 Extra Length 001C (28) │ │ │ │ +53CB6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x53CB6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +53CD1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +53CD3 Length 0009 (9) │ │ │ │ +53CD5 Flags 03 (3) 'Modification Access' │ │ │ │ +53CD6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53CDA Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +53CDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +53CE0 Length 000B (11) │ │ │ │ +53CE2 Version 01 (1) │ │ │ │ +53CE3 UID Size 04 (4) │ │ │ │ +53CE4 UID 00000000 (0) │ │ │ │ +53CE8 GID Size 04 (4) │ │ │ │ +53CE9 GID 00000000 (0) │ │ │ │ +53CED PAYLOAD │ │ │ │ + │ │ │ │ +54152 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +54156 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +54157 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +54158 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5415A Compression Method 0008 (8) 'Deflated' │ │ │ │ +5415C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +54160 CRC 14A45306 (346313478) │ │ │ │ +54164 Compressed Size 000016F3 (5875) │ │ │ │ +54168 Uncompressed Size 00007A6B (31339) │ │ │ │ +5416C Filename Length 001F (31) │ │ │ │ +5416E Extra Length 001C (28) │ │ │ │ +54170 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x54170: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5418F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +54191 Length 0009 (9) │ │ │ │ +54193 Flags 03 (3) 'Modification Access' │ │ │ │ +54194 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +54198 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5419C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5419E Length 000B (11) │ │ │ │ +541A0 Version 01 (1) │ │ │ │ +541A1 UID Size 04 (4) │ │ │ │ +541A2 UID 00000000 (0) │ │ │ │ +541A6 GID Size 04 (4) │ │ │ │ +541A7 GID 00000000 (0) │ │ │ │ +541AB PAYLOAD │ │ │ │ + │ │ │ │ +5589E LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +558A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +558A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +558A4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +558A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +558A8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +558AC CRC 5FD9B2F8 (1608102648) │ │ │ │ +558B0 Compressed Size 00004154 (16724) │ │ │ │ +558B4 Uncompressed Size 0001D15D (119133) │ │ │ │ +558B8 Filename Length 0010 (16) │ │ │ │ +558BA Extra Length 001C (28) │ │ │ │ +558BC Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x558BC: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +558CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +558CE Length 0009 (9) │ │ │ │ +558D0 Flags 03 (3) 'Modification Access' │ │ │ │ +558D1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +558D5 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +558D9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +558DB Length 000B (11) │ │ │ │ +558DD Version 01 (1) │ │ │ │ +558DE UID Size 04 (4) │ │ │ │ +558DF UID 00000000 (0) │ │ │ │ +558E3 GID Size 04 (4) │ │ │ │ +558E4 GID 00000000 (0) │ │ │ │ +558E8 PAYLOAD │ │ │ │ + │ │ │ │ +59A3C LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +59A40 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +59A41 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +59A42 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +59A44 Compression Method 0008 (8) 'Deflated' │ │ │ │ +59A46 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +59A4A CRC 5C6B0F1E (1550520094) │ │ │ │ +59A4E Compressed Size 00000A93 (2707) │ │ │ │ +59A52 Uncompressed Size 00002103 (8451) │ │ │ │ +59A56 Filename Length 0014 (20) │ │ │ │ +59A58 Extra Length 001C (28) │ │ │ │ +59A5A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x59A5A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +59A6E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +59A70 Length 0009 (9) │ │ │ │ +59A72 Flags 03 (3) 'Modification Access' │ │ │ │ +59A73 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +59A77 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +59A7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +59A7D Length 000B (11) │ │ │ │ +59A7F Version 01 (1) │ │ │ │ +59A80 UID Size 04 (4) │ │ │ │ +59A81 UID 00000000 (0) │ │ │ │ +59A85 GID Size 04 (4) │ │ │ │ +59A86 GID 00000000 (0) │ │ │ │ +59A8A PAYLOAD │ │ │ │ + │ │ │ │ +5A51D LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +5A521 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5A522 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5A523 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5A525 Compression Method 0008 (8) 'Deflated' │ │ │ │ +5A527 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5A52B CRC B65A8F9E (3059388318) │ │ │ │ +5A52F Compressed Size 0000B53F (46399) │ │ │ │ +5A533 Uncompressed Size 000418F9 (268537) │ │ │ │ +5A537 Filename Length 0017 (23) │ │ │ │ +5A539 Extra Length 001C (28) │ │ │ │ +5A53B Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5A53B: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5A552 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5A554 Length 0009 (9) │ │ │ │ +5A556 Flags 03 (3) 'Modification Access' │ │ │ │ +5A557 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5A55B Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +5A55F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5A561 Length 000B (11) │ │ │ │ +5A563 Version 01 (1) │ │ │ │ +5A564 UID Size 04 (4) │ │ │ │ +5A565 UID 00000000 (0) │ │ │ │ +5A569 GID Size 04 (4) │ │ │ │ +5A56A GID 00000000 (0) │ │ │ │ +5A56E PAYLOAD │ │ │ │ + │ │ │ │ +65AAD LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +65AB1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +65AB2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +65AB3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +65AB5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +65AB7 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65ABB CRC ED064A2F (3976612399) │ │ │ │ +65ABF Compressed Size 000003FF (1023) │ │ │ │ +65AC3 Uncompressed Size 0000093B (2363) │ │ │ │ +65AC7 Filename Length 0013 (19) │ │ │ │ +65AC9 Extra Length 001C (28) │ │ │ │ +65ACB Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x65ACB: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +65ADE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +65AE0 Length 0009 (9) │ │ │ │ +65AE2 Flags 03 (3) 'Modification Access' │ │ │ │ +65AE3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65AE7 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65AEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +65AED Length 000B (11) │ │ │ │ +65AEF Version 01 (1) │ │ │ │ +65AF0 UID Size 04 (4) │ │ │ │ +65AF1 UID 00000000 (0) │ │ │ │ +65AF5 GID Size 04 (4) │ │ │ │ +65AF6 GID 00000000 (0) │ │ │ │ +65AFA PAYLOAD │ │ │ │ + │ │ │ │ +65EF9 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +65EFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +65EFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +65EFF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +65F01 Compression Method 0008 (8) 'Deflated' │ │ │ │ +65F03 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65F07 CRC 5E7E8B5A (1585351514) │ │ │ │ +65F0B Compressed Size 000014DA (5338) │ │ │ │ +65F0F Uncompressed Size 00006890 (26768) │ │ │ │ +65F13 Filename Length 0012 (18) │ │ │ │ +65F15 Extra Length 001C (28) │ │ │ │ +65F17 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x65F17: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +65F29 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +65F2B Length 0009 (9) │ │ │ │ +65F2D Flags 03 (3) 'Modification Access' │ │ │ │ +65F2E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65F32 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +65F36 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +65F38 Length 000B (11) │ │ │ │ +65F3A Version 01 (1) │ │ │ │ +65F3B UID Size 04 (4) │ │ │ │ +65F3C UID 00000000 (0) │ │ │ │ +65F40 GID Size 04 (4) │ │ │ │ +65F41 GID 00000000 (0) │ │ │ │ +65F45 PAYLOAD │ │ │ │ + │ │ │ │ +6741F LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +67423 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +67424 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +67425 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +67427 Compression Method 0008 (8) 'Deflated' │ │ │ │ +67429 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6742D CRC 399ACCCF (966446287) │ │ │ │ +67431 Compressed Size 000011E6 (4582) │ │ │ │ +67435 Uncompressed Size 000040F8 (16632) │ │ │ │ +67439 Filename Length 0012 (18) │ │ │ │ +6743B Extra Length 001C (28) │ │ │ │ +6743D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6743D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6744F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +67451 Length 0009 (9) │ │ │ │ +67453 Flags 03 (3) 'Modification Access' │ │ │ │ +67454 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +67458 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6745C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6745E Length 000B (11) │ │ │ │ +67460 Version 01 (1) │ │ │ │ +67461 UID Size 04 (4) │ │ │ │ +67462 UID 00000000 (0) │ │ │ │ +67466 GID Size 04 (4) │ │ │ │ +67467 GID 00000000 (0) │ │ │ │ +6746B PAYLOAD │ │ │ │ + │ │ │ │ +68651 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +68655 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +68656 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +68657 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +68659 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6865B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6865F CRC B73C2316 (3074171670) │ │ │ │ +68663 Compressed Size 000009D8 (2520) │ │ │ │ +68667 Uncompressed Size 00003527 (13607) │ │ │ │ +6866B Filename Length 0019 (25) │ │ │ │ +6866D Extra Length 001C (28) │ │ │ │ +6866F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6866F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +68688 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6868A Length 0009 (9) │ │ │ │ +6868C Flags 03 (3) 'Modification Access' │ │ │ │ +6868D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +68691 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +68695 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +68697 Length 000B (11) │ │ │ │ +68699 Version 01 (1) │ │ │ │ +6869A UID Size 04 (4) │ │ │ │ +6869B UID 00000000 (0) │ │ │ │ +6869F GID Size 04 (4) │ │ │ │ +686A0 GID 00000000 (0) │ │ │ │ +686A4 PAYLOAD │ │ │ │ + │ │ │ │ +6907C LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +69080 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +69081 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +69082 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +69084 Compression Method 0008 (8) 'Deflated' │ │ │ │ +69086 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6908A CRC 708D141A (1888293914) │ │ │ │ +6908E Compressed Size 000018B2 (6322) │ │ │ │ +69092 Uncompressed Size 0000A676 (42614) │ │ │ │ +69096 Filename Length 0019 (25) │ │ │ │ +69098 Extra Length 001C (28) │ │ │ │ +6909A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6909A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +690B3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +690B5 Length 0009 (9) │ │ │ │ +690B7 Flags 03 (3) 'Modification Access' │ │ │ │ +690B8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +690BC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +690C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +690C2 Length 000B (11) │ │ │ │ +690C4 Version 01 (1) │ │ │ │ +690C5 UID Size 04 (4) │ │ │ │ +690C6 UID 00000000 (0) │ │ │ │ +690CA GID Size 04 (4) │ │ │ │ +690CB GID 00000000 (0) │ │ │ │ +690CF PAYLOAD │ │ │ │ + │ │ │ │ +6A981 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +6A985 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6A986 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6A987 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6A989 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6A98B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6A98F CRC 28DDA60C (685614604) │ │ │ │ +6A993 Compressed Size 0000177A (6010) │ │ │ │ +6A997 Uncompressed Size 0000472A (18218) │ │ │ │ +6A99B Filename Length 0014 (20) │ │ │ │ +6A99D Extra Length 001C (28) │ │ │ │ +6A99F Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6A99F: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6A9B3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6A9B5 Length 0009 (9) │ │ │ │ +6A9B7 Flags 03 (3) 'Modification Access' │ │ │ │ +6A9B8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6A9BC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6A9C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6A9C2 Length 000B (11) │ │ │ │ +6A9C4 Version 01 (1) │ │ │ │ +6A9C5 UID Size 04 (4) │ │ │ │ +6A9C6 UID 00000000 (0) │ │ │ │ +6A9CA GID Size 04 (4) │ │ │ │ +6A9CB GID 00000000 (0) │ │ │ │ +6A9CF PAYLOAD │ │ │ │ + │ │ │ │ +6C149 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +6C14D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6C14E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6C14F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6C151 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6C153 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C157 CRC AAF8364A (2868393546) │ │ │ │ +6C15B Compressed Size 00000408 (1032) │ │ │ │ +6C15F Uncompressed Size 00000823 (2083) │ │ │ │ +6C163 Filename Length 001C (28) │ │ │ │ +6C165 Extra Length 001C (28) │ │ │ │ +6C167 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6C167: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6C183 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6C185 Length 0009 (9) │ │ │ │ +6C187 Flags 03 (3) 'Modification Access' │ │ │ │ +6C188 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C18C Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C190 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6C192 Length 000B (11) │ │ │ │ +6C194 Version 01 (1) │ │ │ │ +6C195 UID Size 04 (4) │ │ │ │ +6C196 UID 00000000 (0) │ │ │ │ +6C19A GID Size 04 (4) │ │ │ │ +6C19B GID 00000000 (0) │ │ │ │ +6C19F PAYLOAD │ │ │ │ + │ │ │ │ +6C5A7 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +6C5AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6C5AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6C5AD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6C5AF Compression Method 0008 (8) 'Deflated' │ │ │ │ +6C5B1 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C5B5 CRC 2B886ACB (730360523) │ │ │ │ +6C5B9 Compressed Size 000024C1 (9409) │ │ │ │ +6C5BD Uncompressed Size 0000B659 (46681) │ │ │ │ +6C5C1 Filename Length 001F (31) │ │ │ │ +6C5C3 Extra Length 001C (28) │ │ │ │ +6C5C5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6C5C5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6C5E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6C5E6 Length 0009 (9) │ │ │ │ +6C5E8 Flags 03 (3) 'Modification Access' │ │ │ │ +6C5E9 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C5ED Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6C5F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6C5F3 Length 000B (11) │ │ │ │ +6C5F5 Version 01 (1) │ │ │ │ +6C5F6 UID Size 04 (4) │ │ │ │ +6C5F7 UID 00000000 (0) │ │ │ │ +6C5FB GID Size 04 (4) │ │ │ │ +6C5FC GID 00000000 (0) │ │ │ │ +6C600 PAYLOAD │ │ │ │ + │ │ │ │ +6EAC1 LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +6EAC5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6EAC6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6EAC7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6EAC9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6EACB Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6EACF CRC 0CE5F304 (216396548) │ │ │ │ +6EAD3 Compressed Size 00000E76 (3702) │ │ │ │ +6EAD7 Uncompressed Size 000052D7 (21207) │ │ │ │ +6EADB Filename Length 001F (31) │ │ │ │ +6EADD Extra Length 001C (28) │ │ │ │ +6EADF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6EADF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6EAFE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6EB00 Length 0009 (9) │ │ │ │ +6EB02 Flags 03 (3) 'Modification Access' │ │ │ │ +6EB03 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6EB07 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6EB0B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6EB0D Length 000B (11) │ │ │ │ +6EB0F Version 01 (1) │ │ │ │ +6EB10 UID Size 04 (4) │ │ │ │ +6EB11 UID 00000000 (0) │ │ │ │ +6EB15 GID Size 04 (4) │ │ │ │ +6EB16 GID 00000000 (0) │ │ │ │ +6EB1A PAYLOAD │ │ │ │ + │ │ │ │ +6F990 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +6F994 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6F995 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6F996 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6F998 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6F99A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6F99E CRC 995DD4D2 (2573063378) │ │ │ │ +6F9A2 Compressed Size 00000A42 (2626) │ │ │ │ +6F9A6 Uncompressed Size 00002478 (9336) │ │ │ │ +6F9AA Filename Length 0013 (19) │ │ │ │ +6F9AC Extra Length 001C (28) │ │ │ │ +6F9AE Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6F9AE: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6F9C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6F9C3 Length 0009 (9) │ │ │ │ +6F9C5 Flags 03 (3) 'Modification Access' │ │ │ │ +6F9C6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6F9CA Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +6F9CE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6F9D0 Length 000B (11) │ │ │ │ +6F9D2 Version 01 (1) │ │ │ │ +6F9D3 UID Size 04 (4) │ │ │ │ +6F9D4 UID 00000000 (0) │ │ │ │ +6F9D8 GID Size 04 (4) │ │ │ │ +6F9D9 GID 00000000 (0) │ │ │ │ +6F9DD PAYLOAD │ │ │ │ + │ │ │ │ +7041F LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +70423 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +70424 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +70425 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +70427 Compression Method 0008 (8) 'Deflated' │ │ │ │ +70429 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7042D CRC 7D4C9201 (2102170113) │ │ │ │ +70431 Compressed Size 00002549 (9545) │ │ │ │ +70435 Uncompressed Size 0000B9C7 (47559) │ │ │ │ +70439 Filename Length 0019 (25) │ │ │ │ +7043B Extra Length 001C (28) │ │ │ │ +7043D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7043D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +70456 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +70458 Length 0009 (9) │ │ │ │ +7045A Flags 03 (3) 'Modification Access' │ │ │ │ +7045B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7045F Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +70463 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +70465 Length 000B (11) │ │ │ │ +70467 Version 01 (1) │ │ │ │ +70468 UID Size 04 (4) │ │ │ │ +70469 UID 00000000 (0) │ │ │ │ +7046D GID Size 04 (4) │ │ │ │ +7046E GID 00000000 (0) │ │ │ │ +70472 PAYLOAD │ │ │ │ + │ │ │ │ +729BB LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +729BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +729C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +729C1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +729C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +729C5 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +729C9 CRC 0883F700 (142866176) │ │ │ │ +729CD Compressed Size 00000EF8 (3832) │ │ │ │ +729D1 Uncompressed Size 00003A2A (14890) │ │ │ │ +729D5 Filename Length 0024 (36) │ │ │ │ +729D7 Extra Length 001C (28) │ │ │ │ +729D9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x729D9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +729FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +729FF Length 0009 (9) │ │ │ │ +72A01 Flags 03 (3) 'Modification Access' │ │ │ │ +72A02 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +72A06 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +72A0A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +72A0C Length 000B (11) │ │ │ │ +72A0E Version 01 (1) │ │ │ │ +72A0F UID Size 04 (4) │ │ │ │ +72A10 UID 00000000 (0) │ │ │ │ +72A14 GID Size 04 (4) │ │ │ │ +72A15 GID 00000000 (0) │ │ │ │ +72A19 PAYLOAD │ │ │ │ + │ │ │ │ +73911 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +73915 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +73916 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +73917 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +73919 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7391B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7391F CRC C7E6B489 (3353785481) │ │ │ │ +73923 Compressed Size 00001AB3 (6835) │ │ │ │ +73927 Uncompressed Size 00005F00 (24320) │ │ │ │ +7392B Filename Length 0017 (23) │ │ │ │ +7392D Extra Length 001C (28) │ │ │ │ +7392F Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7392F: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +73946 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +73948 Length 0009 (9) │ │ │ │ +7394A Flags 03 (3) 'Modification Access' │ │ │ │ +7394B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7394F Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +73953 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +73955 Length 000B (11) │ │ │ │ +73957 Version 01 (1) │ │ │ │ +73958 UID Size 04 (4) │ │ │ │ +73959 UID 00000000 (0) │ │ │ │ +7395D GID Size 04 (4) │ │ │ │ +7395E GID 00000000 (0) │ │ │ │ +73962 PAYLOAD │ │ │ │ + │ │ │ │ +75415 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +75419 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7541A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7541B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7541D Compression Method 0008 (8) 'Deflated' │ │ │ │ +7541F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +75423 CRC 11E32AF1 (300100337) │ │ │ │ +75427 Compressed Size 00000ED3 (3795) │ │ │ │ +7542B Uncompressed Size 000038E2 (14562) │ │ │ │ +7542F Filename Length 0023 (35) │ │ │ │ +75431 Extra Length 001C (28) │ │ │ │ +75433 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x75433: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +75456 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +75458 Length 0009 (9) │ │ │ │ +7545A Flags 03 (3) 'Modification Access' │ │ │ │ +7545B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7545F Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +75463 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +75465 Length 000B (11) │ │ │ │ +75467 Version 01 (1) │ │ │ │ +75468 UID Size 04 (4) │ │ │ │ +75469 UID 00000000 (0) │ │ │ │ +7546D GID Size 04 (4) │ │ │ │ +7546E GID 00000000 (0) │ │ │ │ +75472 PAYLOAD │ │ │ │ + │ │ │ │ +76345 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +76349 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7634A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7634B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7634D Compression Method 0008 (8) 'Deflated' │ │ │ │ +7634F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +76353 CRC 2DB7929F (767005343) │ │ │ │ +76357 Compressed Size 00000113 (275) │ │ │ │ +7635B Uncompressed Size 000001F3 (499) │ │ │ │ +7635F Filename Length 001B (27) │ │ │ │ +76361 Extra Length 001C (28) │ │ │ │ +76363 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x76363: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7637E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +76380 Length 0009 (9) │ │ │ │ +76382 Flags 03 (3) 'Modification Access' │ │ │ │ +76383 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +76387 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7638B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7638D Length 000B (11) │ │ │ │ +7638F Version 01 (1) │ │ │ │ +76390 UID Size 04 (4) │ │ │ │ +76391 UID 00000000 (0) │ │ │ │ +76395 GID Size 04 (4) │ │ │ │ +76396 GID 00000000 (0) │ │ │ │ +7639A PAYLOAD │ │ │ │ + │ │ │ │ +764AD LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +764B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +764B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +764B3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +764B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +764B7 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +764BB CRC BA52E3CF (3125994447) │ │ │ │ +764BF Compressed Size 00001892 (6290) │ │ │ │ +764C3 Uncompressed Size 00008FAA (36778) │ │ │ │ +764C7 Filename Length 001D (29) │ │ │ │ +764C9 Extra Length 001C (28) │ │ │ │ +764CB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x764CB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +764E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +764EA Length 0009 (9) │ │ │ │ +764EC Flags 03 (3) 'Modification Access' │ │ │ │ +764ED Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +764F1 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +764F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +764F7 Length 000B (11) │ │ │ │ +764F9 Version 01 (1) │ │ │ │ +764FA UID Size 04 (4) │ │ │ │ +764FB UID 00000000 (0) │ │ │ │ +764FF GID Size 04 (4) │ │ │ │ +76500 GID 00000000 (0) │ │ │ │ +76504 PAYLOAD │ │ │ │ + │ │ │ │ +77D96 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +77D9A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +77D9B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +77D9C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +77D9E Compression Method 0008 (8) 'Deflated' │ │ │ │ +77DA0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +77DA4 CRC 227D9DE1 (578657761) │ │ │ │ +77DA8 Compressed Size 0000164B (5707) │ │ │ │ +77DAC Uncompressed Size 00003A99 (15001) │ │ │ │ +77DB0 Filename Length 0015 (21) │ │ │ │ +77DB2 Extra Length 001C (28) │ │ │ │ +77DB4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x77DB4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +77DC9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +77DCB Length 0009 (9) │ │ │ │ +77DCD Flags 03 (3) 'Modification Access' │ │ │ │ +77DCE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +77DD2 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +77DD6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +77DD8 Length 000B (11) │ │ │ │ +77DDA Version 01 (1) │ │ │ │ +77DDB UID Size 04 (4) │ │ │ │ +77DDC UID 00000000 (0) │ │ │ │ +77DE0 GID Size 04 (4) │ │ │ │ +77DE1 GID 00000000 (0) │ │ │ │ +77DE5 PAYLOAD │ │ │ │ + │ │ │ │ +79430 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +79434 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +79435 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +79436 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +79438 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7943A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7943E CRC 51B591AB (1370853803) │ │ │ │ +79442 Compressed Size 00003D36 (15670) │ │ │ │ +79446 Uncompressed Size 0001219B (74139) │ │ │ │ +7944A Filename Length 0016 (22) │ │ │ │ +7944C Extra Length 001C (28) │ │ │ │ +7944E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7944E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +79464 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +79466 Length 0009 (9) │ │ │ │ +79468 Flags 03 (3) 'Modification Access' │ │ │ │ +79469 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7946D Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +79471 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +79473 Length 000B (11) │ │ │ │ +79475 Version 01 (1) │ │ │ │ +79476 UID Size 04 (4) │ │ │ │ +79477 UID 00000000 (0) │ │ │ │ +7947B GID Size 04 (4) │ │ │ │ +7947C GID 00000000 (0) │ │ │ │ +79480 PAYLOAD │ │ │ │ + │ │ │ │ +7D1B6 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +7D1BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7D1BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7D1BC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7D1BE Compression Method 0008 (8) 'Deflated' │ │ │ │ +7D1C0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7D1C4 CRC F90938D5 (4178131157) │ │ │ │ +7D1C8 Compressed Size 00003E8A (16010) │ │ │ │ +7D1CC Uncompressed Size 0001C179 (115065) │ │ │ │ +7D1D0 Filename Length 0019 (25) │ │ │ │ +7D1D2 Extra Length 001C (28) │ │ │ │ +7D1D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7D1D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7D1ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7D1EF Length 0009 (9) │ │ │ │ +7D1F1 Flags 03 (3) 'Modification Access' │ │ │ │ +7D1F2 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7D1F6 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +7D1FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7D1FC Length 000B (11) │ │ │ │ +7D1FE Version 01 (1) │ │ │ │ +7D1FF UID Size 04 (4) │ │ │ │ +7D200 UID 00000000 (0) │ │ │ │ +7D204 GID Size 04 (4) │ │ │ │ +7D205 GID 00000000 (0) │ │ │ │ +7D209 PAYLOAD │ │ │ │ + │ │ │ │ +81093 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ +81097 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81098 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81099 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8109B Compression Method 0008 (8) 'Deflated' │ │ │ │ +8109D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +810A1 CRC B639B633 (3057235507) │ │ │ │ +810A5 Compressed Size 0000088E (2190) │ │ │ │ +810A9 Uncompressed Size 0000362C (13868) │ │ │ │ +810AD Filename Length 0011 (17) │ │ │ │ +810AF Extra Length 001C (28) │ │ │ │ +810B1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x810B1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +810C2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +810C4 Length 0009 (9) │ │ │ │ +810C6 Flags 03 (3) 'Modification Access' │ │ │ │ +810C7 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +810CB Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +810CF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +810D1 Length 000B (11) │ │ │ │ +810D3 Version 01 (1) │ │ │ │ +810D4 UID Size 04 (4) │ │ │ │ +810D5 UID 00000000 (0) │ │ │ │ +810D9 GID Size 04 (4) │ │ │ │ +810DA GID 00000000 (0) │ │ │ │ +810DE PAYLOAD │ │ │ │ + │ │ │ │ +8196C LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +81970 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81971 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81972 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +81974 Compression Method 0008 (8) 'Deflated' │ │ │ │ +81976 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8197A CRC 37D9EC62 (937028706) │ │ │ │ +8197E Compressed Size 000051AF (20911) │ │ │ │ +81982 Uncompressed Size 0001FBDD (130013) │ │ │ │ +81986 Filename Length 0015 (21) │ │ │ │ +81988 Extra Length 001C (28) │ │ │ │ +8198A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8198A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8199F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +819A1 Length 0009 (9) │ │ │ │ +819A3 Flags 03 (3) 'Modification Access' │ │ │ │ +819A4 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +819A8 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +819AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +819AE Length 000B (11) │ │ │ │ +819B0 Version 01 (1) │ │ │ │ +819B1 UID Size 04 (4) │ │ │ │ +819B2 UID 00000000 (0) │ │ │ │ +819B6 GID Size 04 (4) │ │ │ │ +819B7 GID 00000000 (0) │ │ │ │ +819BB PAYLOAD │ │ │ │ + │ │ │ │ +86B6A LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +86B6E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +86B6F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +86B70 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +86B72 Compression Method 0008 (8) 'Deflated' │ │ │ │ +86B74 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +86B78 CRC 2E361F32 (775298866) │ │ │ │ +86B7C Compressed Size 00001AFB (6907) │ │ │ │ +86B80 Uncompressed Size 00008257 (33367) │ │ │ │ +86B84 Filename Length 0019 (25) │ │ │ │ +86B86 Extra Length 001C (28) │ │ │ │ +86B88 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x86B88: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +86BA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +86BA3 Length 0009 (9) │ │ │ │ +86BA5 Flags 03 (3) 'Modification Access' │ │ │ │ +86BA6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +86BAA Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +86BAE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +86BB0 Length 000B (11) │ │ │ │ +86BB2 Version 01 (1) │ │ │ │ +86BB3 UID Size 04 (4) │ │ │ │ +86BB4 UID 00000000 (0) │ │ │ │ +86BB8 GID Size 04 (4) │ │ │ │ +86BB9 GID 00000000 (0) │ │ │ │ +86BBD PAYLOAD │ │ │ │ + │ │ │ │ +886B8 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +886BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +886BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +886BE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +886C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +886C2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +886C6 CRC 49C26BB3 (1237478323) │ │ │ │ +886CA Compressed Size 00000D95 (3477) │ │ │ │ +886CE Uncompressed Size 00002E9D (11933) │ │ │ │ +886D2 Filename Length 0018 (24) │ │ │ │ +886D4 Extra Length 001C (28) │ │ │ │ +886D6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x886D6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +886EE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +886F0 Length 0009 (9) │ │ │ │ +886F2 Flags 03 (3) 'Modification Access' │ │ │ │ +886F3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +886F7 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +886FB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +886FD Length 000B (11) │ │ │ │ +886FF Version 01 (1) │ │ │ │ +88700 UID Size 04 (4) │ │ │ │ +88701 UID 00000000 (0) │ │ │ │ +88705 GID Size 04 (4) │ │ │ │ +88706 GID 00000000 (0) │ │ │ │ +8870A PAYLOAD │ │ │ │ + │ │ │ │ +8949F LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +894A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +894A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +894A5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +894A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +894A9 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +894AD CRC 42771396 (1115100054) │ │ │ │ +894B1 Compressed Size 000001DF (479) │ │ │ │ +894B5 Uncompressed Size 00000321 (801) │ │ │ │ +894B9 Filename Length 0011 (17) │ │ │ │ +894BB Extra Length 001C (28) │ │ │ │ +894BD Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x894BD: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +894CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +894D0 Length 0009 (9) │ │ │ │ +894D2 Flags 03 (3) 'Modification Access' │ │ │ │ +894D3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +894D7 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +894DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +894DD Length 000B (11) │ │ │ │ +894DF Version 01 (1) │ │ │ │ +894E0 UID Size 04 (4) │ │ │ │ +894E1 UID 00000000 (0) │ │ │ │ +894E5 GID Size 04 (4) │ │ │ │ +894E6 GID 00000000 (0) │ │ │ │ +894EA PAYLOAD │ │ │ │ + │ │ │ │ +896C9 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +896CD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +896CE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +896CF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +896D1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +896D3 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +896D7 CRC DB0CDFF8 (3675054072) │ │ │ │ +896DB Compressed Size 000006BC (1724) │ │ │ │ +896DF Uncompressed Size 0000141C (5148) │ │ │ │ +896E3 Filename Length 0019 (25) │ │ │ │ +896E5 Extra Length 001C (28) │ │ │ │ +896E7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x896E7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +89700 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +89702 Length 0009 (9) │ │ │ │ +89704 Flags 03 (3) 'Modification Access' │ │ │ │ +89705 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +89709 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8970D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8970F Length 000B (11) │ │ │ │ +89711 Version 01 (1) │ │ │ │ +89712 UID Size 04 (4) │ │ │ │ +89713 UID 00000000 (0) │ │ │ │ +89717 GID Size 04 (4) │ │ │ │ +89718 GID 00000000 (0) │ │ │ │ +8971C PAYLOAD │ │ │ │ + │ │ │ │ +89DD8 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +89DDC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +89DDD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +89DDE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +89DE0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +89DE2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +89DE6 CRC 65917CFB (1704033531) │ │ │ │ +89DEA Compressed Size 00001B89 (7049) │ │ │ │ +89DEE Uncompressed Size 00009F5D (40797) │ │ │ │ +89DF2 Filename Length 0018 (24) │ │ │ │ +89DF4 Extra Length 001C (28) │ │ │ │ +89DF6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x89DF6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +89E0E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +89E10 Length 0009 (9) │ │ │ │ +89E12 Flags 03 (3) 'Modification Access' │ │ │ │ +89E13 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +89E17 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +89E1B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +89E1D Length 000B (11) │ │ │ │ +89E1F Version 01 (1) │ │ │ │ +89E20 UID Size 04 (4) │ │ │ │ +89E21 UID 00000000 (0) │ │ │ │ +89E25 GID Size 04 (4) │ │ │ │ +89E26 GID 00000000 (0) │ │ │ │ +89E2A PAYLOAD │ │ │ │ + │ │ │ │ +8B9B3 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +8B9B7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8B9B8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8B9B9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8B9BB Compression Method 0008 (8) 'Deflated' │ │ │ │ +8B9BD Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8B9C1 CRC FCA2FA5B (4238539355) │ │ │ │ +8B9C5 Compressed Size 000016FA (5882) │ │ │ │ +8B9C9 Uncompressed Size 00008B10 (35600) │ │ │ │ +8B9CD Filename Length 0012 (18) │ │ │ │ +8B9CF Extra Length 001C (28) │ │ │ │ +8B9D1 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8B9D1: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8B9E3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8B9E5 Length 0009 (9) │ │ │ │ +8B9E7 Flags 03 (3) 'Modification Access' │ │ │ │ +8B9E8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8B9EC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8B9F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8B9F2 Length 000B (11) │ │ │ │ +8B9F4 Version 01 (1) │ │ │ │ +8B9F5 UID Size 04 (4) │ │ │ │ +8B9F6 UID 00000000 (0) │ │ │ │ +8B9FA GID Size 04 (4) │ │ │ │ +8B9FB GID 00000000 (0) │ │ │ │ +8B9FF PAYLOAD │ │ │ │ + │ │ │ │ +8D0F9 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +8D0FD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8D0FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8D0FF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8D101 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8D103 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8D107 CRC 24445419 (608457753) │ │ │ │ +8D10B Compressed Size 00001E10 (7696) │ │ │ │ +8D10F Uncompressed Size 00008801 (34817) │ │ │ │ +8D113 Filename Length 0016 (22) │ │ │ │ +8D115 Extra Length 001C (28) │ │ │ │ +8D117 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8D117: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8D12D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8D12F Length 0009 (9) │ │ │ │ +8D131 Flags 03 (3) 'Modification Access' │ │ │ │ +8D132 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8D136 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8D13A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8D13C Length 000B (11) │ │ │ │ +8D13E Version 01 (1) │ │ │ │ +8D13F UID Size 04 (4) │ │ │ │ +8D140 UID 00000000 (0) │ │ │ │ +8D144 GID Size 04 (4) │ │ │ │ +8D145 GID 00000000 (0) │ │ │ │ +8D149 PAYLOAD │ │ │ │ + │ │ │ │ +8EF59 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +8EF5D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8EF5E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8EF5F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8EF61 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8EF63 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8EF67 CRC A41B923B (2753270331) │ │ │ │ +8EF6B Compressed Size 000029AB (10667) │ │ │ │ +8EF6F Uncompressed Size 0000D04D (53325) │ │ │ │ +8EF73 Filename Length 001A (26) │ │ │ │ +8EF75 Extra Length 001C (28) │ │ │ │ +8EF77 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8EF77: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8EF91 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8EF93 Length 0009 (9) │ │ │ │ +8EF95 Flags 03 (3) 'Modification Access' │ │ │ │ +8EF96 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8EF9A Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +8EF9E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8EFA0 Length 000B (11) │ │ │ │ +8EFA2 Version 01 (1) │ │ │ │ +8EFA3 UID Size 04 (4) │ │ │ │ +8EFA4 UID 00000000 (0) │ │ │ │ +8EFA8 GID Size 04 (4) │ │ │ │ +8EFA9 GID 00000000 (0) │ │ │ │ +8EFAD PAYLOAD │ │ │ │ + │ │ │ │ +91958 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +9195C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9195D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9195E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +91960 Compression Method 0008 (8) 'Deflated' │ │ │ │ +91962 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +91966 CRC 25687D13 (627604755) │ │ │ │ +9196A Compressed Size 000009AA (2474) │ │ │ │ +9196E Uncompressed Size 00001DB4 (7604) │ │ │ │ +91972 Filename Length 0018 (24) │ │ │ │ +91974 Extra Length 001C (28) │ │ │ │ +91976 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x91976: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9198E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +91990 Length 0009 (9) │ │ │ │ +91992 Flags 03 (3) 'Modification Access' │ │ │ │ +91993 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +91997 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +9199B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9199D Length 000B (11) │ │ │ │ +9199F Version 01 (1) │ │ │ │ +919A0 UID Size 04 (4) │ │ │ │ +919A1 UID 00000000 (0) │ │ │ │ +919A5 GID Size 04 (4) │ │ │ │ +919A6 GID 00000000 (0) │ │ │ │ +919AA PAYLOAD │ │ │ │ + │ │ │ │ +92354 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +92358 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +92359 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9235A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9235C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9235E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +92362 CRC F0556E9A (4032130714) │ │ │ │ +92366 Compressed Size 000152EE (86766) │ │ │ │ +9236A Uncompressed Size 000159F8 (88568) │ │ │ │ +9236E Filename Length 001E (30) │ │ │ │ +92370 Extra Length 001C (28) │ │ │ │ +92372 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x92372: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +92390 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +92392 Length 0009 (9) │ │ │ │ +92394 Flags 03 (3) 'Modification Access' │ │ │ │ +92395 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +92399 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +9239D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9239F Length 000B (11) │ │ │ │ +923A1 Version 01 (1) │ │ │ │ +923A2 UID Size 04 (4) │ │ │ │ +923A3 UID 00000000 (0) │ │ │ │ +923A7 GID Size 04 (4) │ │ │ │ +923A8 GID 00000000 (0) │ │ │ │ +923AC PAYLOAD │ │ │ │ + │ │ │ │ +A769A LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +A769E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A769F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A76A0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A76A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A76A4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A76A8 CRC F5E2129F (4125233823) │ │ │ │ +A76AC Compressed Size 000016BC (5820) │ │ │ │ +A76B0 Uncompressed Size 000016CD (5837) │ │ │ │ +A76B4 Filename Length 0015 (21) │ │ │ │ +A76B6 Extra Length 001C (28) │ │ │ │ +A76B8 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA76B8: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A76CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A76CF Length 0009 (9) │ │ │ │ +A76D1 Flags 03 (3) 'Modification Access' │ │ │ │ +A76D2 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A76D6 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A76DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A76DC Length 000B (11) │ │ │ │ +A76DE Version 01 (1) │ │ │ │ +A76DF UID Size 04 (4) │ │ │ │ +A76E0 UID 00000000 (0) │ │ │ │ +A76E4 GID Size 04 (4) │ │ │ │ +A76E5 GID 00000000 (0) │ │ │ │ +A76E9 PAYLOAD │ │ │ │ + │ │ │ │ +A8DA5 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +A8DA9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A8DAA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A8DAB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A8DAD Compression Method 0008 (8) 'Deflated' │ │ │ │ +A8DAF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A8DB3 CRC F5E2129F (4125233823) │ │ │ │ +A8DB7 Compressed Size 000016BC (5820) │ │ │ │ +A8DBB Uncompressed Size 000016CD (5837) │ │ │ │ +A8DBF Filename Length 001C (28) │ │ │ │ +A8DC1 Extra Length 001C (28) │ │ │ │ +A8DC3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA8DC3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A8DDF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A8DE1 Length 0009 (9) │ │ │ │ +A8DE3 Flags 03 (3) 'Modification Access' │ │ │ │ +A8DE4 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A8DE8 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +A8DEC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A8DEE Length 000B (11) │ │ │ │ +A8DF0 Version 01 (1) │ │ │ │ +A8DF1 UID Size 04 (4) │ │ │ │ +A8DF2 UID 00000000 (0) │ │ │ │ +A8DF6 GID Size 04 (4) │ │ │ │ +A8DF7 GID 00000000 (0) │ │ │ │ +A8DFB PAYLOAD │ │ │ │ + │ │ │ │ +AA4B7 LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +AA4BB Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AA4BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AA4BD General Purpose Flag 0000 (0) │ │ │ │ +AA4BF Compression Method 0000 (0) 'Stored' │ │ │ │ +AA4C1 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AA4C5 CRC FC95F24B (4237685323) │ │ │ │ +AA4C9 Compressed Size 00001B84 (7044) │ │ │ │ +AA4CD Uncompressed Size 00001B84 (7044) │ │ │ │ +AA4D1 Filename Length 0016 (22) │ │ │ │ +AA4D3 Extra Length 001C (28) │ │ │ │ +AA4D5 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAA4D5: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AA4EB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AA4ED Length 0009 (9) │ │ │ │ +AA4EF Flags 03 (3) 'Modification Access' │ │ │ │ +AA4F0 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AA4F4 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AA4F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AA4FA Length 000B (11) │ │ │ │ +AA4FC Version 01 (1) │ │ │ │ +AA4FD UID Size 04 (4) │ │ │ │ +AA4FE UID 00000000 (0) │ │ │ │ +AA502 GID Size 04 (4) │ │ │ │ +AA503 GID 00000000 (0) │ │ │ │ +AA507 PAYLOAD │ │ │ │ + │ │ │ │ +AC08B LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +AC08F Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AC090 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AC091 General Purpose Flag 0000 (0) │ │ │ │ +AC093 Compression Method 0000 (0) 'Stored' │ │ │ │ +AC095 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AC099 CRC D0D71F86 (3503759238) │ │ │ │ +AC09D Compressed Size 00000B7B (2939) │ │ │ │ +AC0A1 Uncompressed Size 00000B7B (2939) │ │ │ │ +AC0A5 Filename Length 0016 (22) │ │ │ │ +AC0A7 Extra Length 001C (28) │ │ │ │ +AC0A9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAC0A9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AC0BF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AC0C1 Length 0009 (9) │ │ │ │ +AC0C3 Flags 03 (3) 'Modification Access' │ │ │ │ +AC0C4 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AC0C8 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AC0CC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AC0CE Length 000B (11) │ │ │ │ +AC0D0 Version 01 (1) │ │ │ │ +AC0D1 UID Size 04 (4) │ │ │ │ +AC0D2 UID 00000000 (0) │ │ │ │ +AC0D6 GID Size 04 (4) │ │ │ │ +AC0D7 GID 00000000 (0) │ │ │ │ +AC0DB PAYLOAD │ │ │ │ + │ │ │ │ +ACC56 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +ACC5A Extract Zip Spec 0A (10) '1.0' │ │ │ │ +ACC5B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +ACC5C General Purpose Flag 0000 (0) │ │ │ │ +ACC5E Compression Method 0000 (0) 'Stored' │ │ │ │ +ACC60 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +ACC64 CRC FFF9C4D2 (4294558930) │ │ │ │ +ACC68 Compressed Size 0000138F (5007) │ │ │ │ +ACC6C Uncompressed Size 0000138F (5007) │ │ │ │ +ACC70 Filename Length 0016 (22) │ │ │ │ +ACC72 Extra Length 001C (28) │ │ │ │ +ACC74 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xACC74: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +ACC8A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +ACC8C Length 0009 (9) │ │ │ │ +ACC8E Flags 03 (3) 'Modification Access' │ │ │ │ +ACC8F Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +ACC93 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +ACC97 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +ACC99 Length 000B (11) │ │ │ │ +ACC9B Version 01 (1) │ │ │ │ +ACC9C UID Size 04 (4) │ │ │ │ +ACC9D UID 00000000 (0) │ │ │ │ +ACCA1 GID Size 04 (4) │ │ │ │ +ACCA2 GID 00000000 (0) │ │ │ │ +ACCA6 PAYLOAD │ │ │ │ + │ │ │ │ +AE035 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +AE039 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AE03A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AE03B General Purpose Flag 0000 (0) │ │ │ │ +AE03D Compression Method 0000 (0) 'Stored' │ │ │ │ +AE03F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AE043 CRC A1037E8E (2701360782) │ │ │ │ +AE047 Compressed Size 0000145E (5214) │ │ │ │ +AE04B Uncompressed Size 0000145E (5214) │ │ │ │ +AE04F Filename Length 0016 (22) │ │ │ │ +AE051 Extra Length 001C (28) │ │ │ │ +AE053 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAE053: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AE069 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AE06B Length 0009 (9) │ │ │ │ +AE06D Flags 03 (3) 'Modification Access' │ │ │ │ +AE06E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AE072 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AE076 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AE078 Length 000B (11) │ │ │ │ +AE07A Version 01 (1) │ │ │ │ +AE07B UID Size 04 (4) │ │ │ │ +AE07C UID 00000000 (0) │ │ │ │ +AE080 GID Size 04 (4) │ │ │ │ +AE081 GID 00000000 (0) │ │ │ │ +AE085 PAYLOAD │ │ │ │ + │ │ │ │ +AF4E3 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +AF4E7 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AF4E8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AF4E9 General Purpose Flag 0000 (0) │ │ │ │ +AF4EB Compression Method 0000 (0) 'Stored' │ │ │ │ +AF4ED Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AF4F1 CRC 5E9E64F1 (1587438833) │ │ │ │ +AF4F5 Compressed Size 000008EC (2284) │ │ │ │ +AF4F9 Uncompressed Size 000008EC (2284) │ │ │ │ +AF4FD Filename Length 0016 (22) │ │ │ │ +AF4FF Extra Length 001C (28) │ │ │ │ +AF501 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAF501: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AF517 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AF519 Length 0009 (9) │ │ │ │ +AF51B Flags 03 (3) 'Modification Access' │ │ │ │ +AF51C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AF520 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AF524 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AF526 Length 000B (11) │ │ │ │ +AF528 Version 01 (1) │ │ │ │ +AF529 UID Size 04 (4) │ │ │ │ +AF52A UID 00000000 (0) │ │ │ │ +AF52E GID Size 04 (4) │ │ │ │ +AF52F GID 00000000 (0) │ │ │ │ +AF533 PAYLOAD │ │ │ │ + │ │ │ │ +AFE1F LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ +AFE23 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AFE24 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AFE25 General Purpose Flag 0000 (0) │ │ │ │ +AFE27 Compression Method 0000 (0) 'Stored' │ │ │ │ +AFE29 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AFE2D CRC 42E340AB (1122189483) │ │ │ │ +AFE31 Compressed Size 00001F2E (7982) │ │ │ │ +AFE35 Uncompressed Size 00001F2E (7982) │ │ │ │ +AFE39 Filename Length 001E (30) │ │ │ │ +AFE3B Extra Length 001C (28) │ │ │ │ +AFE3D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAFE3D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AFE5B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AFE5D Length 0009 (9) │ │ │ │ +AFE5F Flags 03 (3) 'Modification Access' │ │ │ │ +AFE60 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AFE64 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +AFE68 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AFE6A Length 000B (11) │ │ │ │ +AFE6C Version 01 (1) │ │ │ │ +AFE6D UID Size 04 (4) │ │ │ │ +AFE6E UID 00000000 (0) │ │ │ │ +AFE72 GID Size 04 (4) │ │ │ │ +AFE73 GID 00000000 (0) │ │ │ │ +AFE77 PAYLOAD │ │ │ │ + │ │ │ │ +B1DA5 LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ +B1DA9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B1DAA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B1DAB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B1DAD Compression Method 0008 (8) 'Deflated' │ │ │ │ +B1DAF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B1DB3 CRC CDFF1E8F (3456048783) │ │ │ │ +B1DB7 Compressed Size 00003D61 (15713) │ │ │ │ +B1DBB Uncompressed Size 0001664D (91725) │ │ │ │ +B1DBF Filename Length 001A (26) │ │ │ │ +B1DC1 Extra Length 001C (28) │ │ │ │ +B1DC3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB1DC3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B1DDD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B1DDF Length 0009 (9) │ │ │ │ +B1DE1 Flags 03 (3) 'Modification Access' │ │ │ │ +B1DE2 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B1DE6 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B1DEA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B1DEC Length 000B (11) │ │ │ │ +B1DEE Version 01 (1) │ │ │ │ +B1DEF UID Size 04 (4) │ │ │ │ +B1DF0 UID 00000000 (0) │ │ │ │ +B1DF4 GID Size 04 (4) │ │ │ │ +B1DF5 GID 00000000 (0) │ │ │ │ +B1DF9 PAYLOAD │ │ │ │ + │ │ │ │ +B5B5A LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ +B5B5E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B5B5F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B5B60 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B5B62 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B5B64 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B5B68 CRC F3D8ADF1 (4091063793) │ │ │ │ +B5B6C Compressed Size 000029CE (10702) │ │ │ │ +B5B70 Uncompressed Size 0000BB37 (47927) │ │ │ │ +B5B74 Filename Length 0018 (24) │ │ │ │ +B5B76 Extra Length 001C (28) │ │ │ │ +B5B78 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB5B78: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B5B90 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B5B92 Length 0009 (9) │ │ │ │ +B5B94 Flags 03 (3) 'Modification Access' │ │ │ │ +B5B95 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B5B99 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B5B9D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B5B9F Length 000B (11) │ │ │ │ +B5BA1 Version 01 (1) │ │ │ │ +B5BA2 UID Size 04 (4) │ │ │ │ +B5BA3 UID 00000000 (0) │ │ │ │ +B5BA7 GID Size 04 (4) │ │ │ │ +B5BA8 GID 00000000 (0) │ │ │ │ +B5BAC PAYLOAD │ │ │ │ + │ │ │ │ +B857A LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ +B857E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B857F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8580 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8582 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8584 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8588 CRC DCB3B516 (3702764822) │ │ │ │ +B858C Compressed Size 000000AE (174) │ │ │ │ +B8590 Uncompressed Size 000000FC (252) │ │ │ │ +B8594 Filename Length 0016 (22) │ │ │ │ +B8596 Extra Length 001C (28) │ │ │ │ +B8598 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8598: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B85AE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B85B0 Length 0009 (9) │ │ │ │ +B85B2 Flags 03 (3) 'Modification Access' │ │ │ │ +B85B3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B85B7 Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B85BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B85BD Length 000B (11) │ │ │ │ +B85BF Version 01 (1) │ │ │ │ +B85C0 UID Size 04 (4) │ │ │ │ +B85C1 UID 00000000 (0) │ │ │ │ +B85C5 GID Size 04 (4) │ │ │ │ +B85C6 GID 00000000 (0) │ │ │ │ +B85CA PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -B869A LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ -B869E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B869F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B86A0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B86A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B86A4 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B86A8 CRC 58439733 (1480824627) │ │ │ │ -B86AC Compressed Size 00000077 (119) │ │ │ │ -B86B0 Uncompressed Size 000000A2 (162) │ │ │ │ -B86B4 Filename Length 002D (45) │ │ │ │ -B86B6 Extra Length 001C (28) │ │ │ │ -B86B8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB86B8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B86E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B86E7 Length 0009 (9) │ │ │ │ -B86E9 Flags 03 (3) 'Modification Access' │ │ │ │ -B86EA Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B86EE Access Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B86F2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B86F4 Length 000B (11) │ │ │ │ -B86F6 Version 01 (1) │ │ │ │ -B86F7 UID Size 04 (4) │ │ │ │ -B86F8 UID 00000000 (0) │ │ │ │ -B86FC GID Size 04 (4) │ │ │ │ -B86FD GID 00000000 (0) │ │ │ │ -B8701 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -B8778 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -B877C Created Zip Spec 3D (61) '6.1' │ │ │ │ -B877D Created OS 03 (3) 'Unix' │ │ │ │ -B877E Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B877F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8780 General Purpose Flag 0000 (0) │ │ │ │ -B8782 Compression Method 0000 (0) 'Stored' │ │ │ │ -B8784 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8788 CRC 2CAB616F (749429103) │ │ │ │ -B878C Compressed Size 00000014 (20) │ │ │ │ -B8790 Uncompressed Size 00000014 (20) │ │ │ │ -B8794 Filename Length 0008 (8) │ │ │ │ -B8796 Extra Length 0018 (24) │ │ │ │ -B8798 Comment Length 0000 (0) │ │ │ │ -B879A Disk Start 0000 (0) │ │ │ │ -B879C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B879E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B87A2 Local Header Offset 00000000 (0) │ │ │ │ -B87A6 Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB87A6: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B87AE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B87B0 Length 0005 (5) │ │ │ │ -B87B2 Flags 01 (1) 'Modification' │ │ │ │ -B87B3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B87B7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B87B9 Length 000B (11) │ │ │ │ -B87BB Version 01 (1) │ │ │ │ -B87BC UID Size 04 (4) │ │ │ │ -B87BD UID 00000000 (0) │ │ │ │ -B87C1 GID Size 04 (4) │ │ │ │ -B87C2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B87C6 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -B87CA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B87CB Created OS 03 (3) 'Unix' │ │ │ │ -B87CC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B87CD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B87CE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B87D0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B87D2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B87D6 CRC 87FBD4CF (2281428175) │ │ │ │ -B87DA Compressed Size 00000D27 (3367) │ │ │ │ -B87DE Uncompressed Size 00003931 (14641) │ │ │ │ -B87E2 Filename Length 001B (27) │ │ │ │ -B87E4 Extra Length 0018 (24) │ │ │ │ -B87E6 Comment Length 0000 (0) │ │ │ │ -B87E8 Disk Start 0000 (0) │ │ │ │ -B87EA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B87EC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B87F0 Local Header Offset 00000056 (86) │ │ │ │ -B87F4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB87F4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B880F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8811 Length 0005 (5) │ │ │ │ -B8813 Flags 01 (1) 'Modification' │ │ │ │ -B8814 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8818 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B881A Length 000B (11) │ │ │ │ -B881C Version 01 (1) │ │ │ │ -B881D UID Size 04 (4) │ │ │ │ -B881E UID 00000000 (0) │ │ │ │ -B8822 GID Size 04 (4) │ │ │ │ -B8823 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8827 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -B882B Created Zip Spec 3D (61) '6.1' │ │ │ │ -B882C Created OS 03 (3) 'Unix' │ │ │ │ -B882D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B882E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B882F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8831 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8833 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8837 CRC DE70EF53 (3731943251) │ │ │ │ -B883B Compressed Size 000015AC (5548) │ │ │ │ -B883F Uncompressed Size 00004600 (17920) │ │ │ │ -B8843 Filename Length 0014 (20) │ │ │ │ -B8845 Extra Length 0018 (24) │ │ │ │ -B8847 Comment Length 0000 (0) │ │ │ │ -B8849 Disk Start 0000 (0) │ │ │ │ -B884B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B884D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8851 Local Header Offset 00000DD2 (3538) │ │ │ │ -B8855 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8855: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8869 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B886B Length 0005 (5) │ │ │ │ -B886D Flags 01 (1) 'Modification' │ │ │ │ -B886E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8872 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8874 Length 000B (11) │ │ │ │ -B8876 Version 01 (1) │ │ │ │ -B8877 UID Size 04 (4) │ │ │ │ -B8878 UID 00000000 (0) │ │ │ │ -B887C GID Size 04 (4) │ │ │ │ -B887D GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8881 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -B8885 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8886 Created OS 03 (3) 'Unix' │ │ │ │ -B8887 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8888 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8889 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B888B Compression Method 0008 (8) 'Deflated' │ │ │ │ -B888D Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8891 CRC 4F1D5C6A (1327324266) │ │ │ │ -B8895 Compressed Size 000006D3 (1747) │ │ │ │ -B8899 Uncompressed Size 0000123F (4671) │ │ │ │ -B889D Filename Length 0013 (19) │ │ │ │ -B889F Extra Length 0018 (24) │ │ │ │ -B88A1 Comment Length 0000 (0) │ │ │ │ -B88A3 Disk Start 0000 (0) │ │ │ │ -B88A5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B88A7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B88AB Local Header Offset 000023CC (9164) │ │ │ │ -B88AF Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB88AF: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B88C2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B88C4 Length 0005 (5) │ │ │ │ -B88C6 Flags 01 (1) 'Modification' │ │ │ │ -B88C7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B88CB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B88CD Length 000B (11) │ │ │ │ -B88CF Version 01 (1) │ │ │ │ -B88D0 UID Size 04 (4) │ │ │ │ -B88D1 UID 00000000 (0) │ │ │ │ -B88D5 GID Size 04 (4) │ │ │ │ -B88D6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B88DA CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -B88DE Created Zip Spec 3D (61) '6.1' │ │ │ │ -B88DF Created OS 03 (3) 'Unix' │ │ │ │ -B88E0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B88E1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B88E2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B88E4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B88E6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B88EA CRC CF28943C (3475543100) │ │ │ │ -B88EE Compressed Size 00002E66 (11878) │ │ │ │ -B88F2 Uncompressed Size 0000D4B1 (54449) │ │ │ │ -B88F6 Filename Length 0014 (20) │ │ │ │ -B88F8 Extra Length 0018 (24) │ │ │ │ -B88FA Comment Length 0000 (0) │ │ │ │ -B88FC Disk Start 0000 (0) │ │ │ │ -B88FE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8900 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8904 Local Header Offset 00002AEC (10988) │ │ │ │ -B8908 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8908: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B891C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B891E Length 0005 (5) │ │ │ │ -B8920 Flags 01 (1) 'Modification' │ │ │ │ -B8921 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8925 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8927 Length 000B (11) │ │ │ │ -B8929 Version 01 (1) │ │ │ │ -B892A UID Size 04 (4) │ │ │ │ -B892B UID 00000000 (0) │ │ │ │ -B892F GID Size 04 (4) │ │ │ │ -B8930 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8934 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -B8938 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8939 Created OS 03 (3) 'Unix' │ │ │ │ -B893A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B893B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B893C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B893E Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8940 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8944 CRC 5467FF9A (1416101786) │ │ │ │ -B8948 Compressed Size 000003ED (1005) │ │ │ │ -B894C Uncompressed Size 00000874 (2164) │ │ │ │ -B8950 Filename Length 0014 (20) │ │ │ │ -B8952 Extra Length 0018 (24) │ │ │ │ -B8954 Comment Length 0000 (0) │ │ │ │ -B8956 Disk Start 0000 (0) │ │ │ │ -B8958 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B895A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B895E Local Header Offset 000059A0 (22944) │ │ │ │ -B8962 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8962: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8976 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8978 Length 0005 (5) │ │ │ │ -B897A Flags 01 (1) 'Modification' │ │ │ │ -B897B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B897F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8981 Length 000B (11) │ │ │ │ -B8983 Version 01 (1) │ │ │ │ -B8984 UID Size 04 (4) │ │ │ │ -B8985 UID 00000000 (0) │ │ │ │ -B8989 GID Size 04 (4) │ │ │ │ -B898A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B898E CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -B8992 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8993 Created OS 03 (3) 'Unix' │ │ │ │ -B8994 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8995 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8996 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8998 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B899A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B899E CRC 58D4C95E (1490340190) │ │ │ │ -B89A2 Compressed Size 000001AC (428) │ │ │ │ -B89A6 Uncompressed Size 000002F8 (760) │ │ │ │ -B89AA Filename Length 0011 (17) │ │ │ │ -B89AC Extra Length 0018 (24) │ │ │ │ -B89AE Comment Length 0000 (0) │ │ │ │ -B89B0 Disk Start 0000 (0) │ │ │ │ -B89B2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B89B4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B89B8 Local Header Offset 00005DDB (24027) │ │ │ │ -B89BC Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB89BC: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B89CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B89CF Length 0005 (5) │ │ │ │ -B89D1 Flags 01 (1) 'Modification' │ │ │ │ -B89D2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B89D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B89D8 Length 000B (11) │ │ │ │ -B89DA Version 01 (1) │ │ │ │ -B89DB UID Size 04 (4) │ │ │ │ -B89DC UID 00000000 (0) │ │ │ │ -B89E0 GID Size 04 (4) │ │ │ │ -B89E1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B89E5 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -B89E9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B89EA Created OS 03 (3) 'Unix' │ │ │ │ -B89EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B89EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B89ED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B89EF Compression Method 0008 (8) 'Deflated' │ │ │ │ -B89F1 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B89F5 CRC 1BB0B15A (464564570) │ │ │ │ -B89F9 Compressed Size 000020C5 (8389) │ │ │ │ -B89FD Uncompressed Size 0000B4AE (46254) │ │ │ │ -B8A01 Filename Length 001B (27) │ │ │ │ -B8A03 Extra Length 0018 (24) │ │ │ │ -B8A05 Comment Length 0000 (0) │ │ │ │ -B8A07 Disk Start 0000 (0) │ │ │ │ -B8A09 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8A0B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8A0F Local Header Offset 00005FD2 (24530) │ │ │ │ -B8A13 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8A13: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8A2E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8A30 Length 0005 (5) │ │ │ │ -B8A32 Flags 01 (1) 'Modification' │ │ │ │ -B8A33 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8A37 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8A39 Length 000B (11) │ │ │ │ -B8A3B Version 01 (1) │ │ │ │ -B8A3C UID Size 04 (4) │ │ │ │ -B8A3D UID 00000000 (0) │ │ │ │ -B8A41 GID Size 04 (4) │ │ │ │ -B8A42 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8A46 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -B8A4A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8A4B Created OS 03 (3) 'Unix' │ │ │ │ -B8A4C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8A4D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8A4E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8A50 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8A52 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8A56 CRC 5EB57D9F (1588952479) │ │ │ │ -B8A5A Compressed Size 00000E64 (3684) │ │ │ │ -B8A5E Uncompressed Size 00003090 (12432) │ │ │ │ -B8A62 Filename Length 001D (29) │ │ │ │ -B8A64 Extra Length 0018 (24) │ │ │ │ -B8A66 Comment Length 0000 (0) │ │ │ │ -B8A68 Disk Start 0000 (0) │ │ │ │ -B8A6A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8A6C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8A70 Local Header Offset 000080EC (33004) │ │ │ │ -B8A74 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8A74: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8A91 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8A93 Length 0005 (5) │ │ │ │ -B8A95 Flags 01 (1) 'Modification' │ │ │ │ -B8A96 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8A9A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8A9C Length 000B (11) │ │ │ │ -B8A9E Version 01 (1) │ │ │ │ -B8A9F UID Size 04 (4) │ │ │ │ -B8AA0 UID 00000000 (0) │ │ │ │ -B8AA4 GID Size 04 (4) │ │ │ │ -B8AA5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8AA9 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -B8AAD Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8AAE Created OS 03 (3) 'Unix' │ │ │ │ -B8AAF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8AB0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8AB1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8AB3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8AB5 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8AB9 CRC 15AABA29 (363510313) │ │ │ │ -B8ABD Compressed Size 00000991 (2449) │ │ │ │ -B8AC1 Uncompressed Size 00001D3B (7483) │ │ │ │ -B8AC5 Filename Length 0019 (25) │ │ │ │ -B8AC7 Extra Length 0018 (24) │ │ │ │ -B8AC9 Comment Length 0000 (0) │ │ │ │ -B8ACB Disk Start 0000 (0) │ │ │ │ -B8ACD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8ACF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8AD3 Local Header Offset 00008FA7 (36775) │ │ │ │ -B8AD7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8AD7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8AF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8AF2 Length 0005 (5) │ │ │ │ -B8AF4 Flags 01 (1) 'Modification' │ │ │ │ -B8AF5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8AF9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8AFB Length 000B (11) │ │ │ │ -B8AFD Version 01 (1) │ │ │ │ -B8AFE UID Size 04 (4) │ │ │ │ -B8AFF UID 00000000 (0) │ │ │ │ -B8B03 GID Size 04 (4) │ │ │ │ -B8B04 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8B08 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -B8B0C Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8B0D Created OS 03 (3) 'Unix' │ │ │ │ -B8B0E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8B0F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8B10 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8B12 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8B14 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8B18 CRC B84AAC32 (3091901490) │ │ │ │ -B8B1C Compressed Size 00003877 (14455) │ │ │ │ -B8B20 Uncompressed Size 0000F7F2 (63474) │ │ │ │ -B8B24 Filename Length 0015 (21) │ │ │ │ -B8B26 Extra Length 0018 (24) │ │ │ │ -B8B28 Comment Length 0000 (0) │ │ │ │ -B8B2A Disk Start 0000 (0) │ │ │ │ -B8B2C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8B2E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8B32 Local Header Offset 0000998B (39307) │ │ │ │ -B8B36 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8B36: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8B4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8B4D Length 0005 (5) │ │ │ │ -B8B4F Flags 01 (1) 'Modification' │ │ │ │ -B8B50 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8B54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8B56 Length 000B (11) │ │ │ │ -B8B58 Version 01 (1) │ │ │ │ -B8B59 UID Size 04 (4) │ │ │ │ -B8B5A UID 00000000 (0) │ │ │ │ -B8B5E GID Size 04 (4) │ │ │ │ -B8B5F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8B63 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -B8B67 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8B68 Created OS 03 (3) 'Unix' │ │ │ │ -B8B69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8B6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8B6B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8B6D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8B6F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8B73 CRC F93107F8 (4180740088) │ │ │ │ -B8B77 Compressed Size 0000AB04 (43780) │ │ │ │ -B8B7B Uncompressed Size 0003E04F (254031) │ │ │ │ -B8B7F Filename Length 0012 (18) │ │ │ │ -B8B81 Extra Length 0018 (24) │ │ │ │ -B8B83 Comment Length 0000 (0) │ │ │ │ -B8B85 Disk Start 0000 (0) │ │ │ │ -B8B87 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8B89 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8B8D Local Header Offset 0000D251 (53841) │ │ │ │ -B8B91 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8B91: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8BA3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8BA5 Length 0005 (5) │ │ │ │ -B8BA7 Flags 01 (1) 'Modification' │ │ │ │ -B8BA8 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8BAC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8BAE Length 000B (11) │ │ │ │ -B8BB0 Version 01 (1) │ │ │ │ -B8BB1 UID Size 04 (4) │ │ │ │ -B8BB2 UID 00000000 (0) │ │ │ │ -B8BB6 GID Size 04 (4) │ │ │ │ -B8BB7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8BBB CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -B8BBF Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8BC0 Created OS 03 (3) 'Unix' │ │ │ │ -B8BC1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8BC2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8BC3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8BC5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8BC7 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8BCB CRC 86F0540D (2263897101) │ │ │ │ -B8BCF Compressed Size 00003B0A (15114) │ │ │ │ -B8BD3 Uncompressed Size 0001B46A (111722) │ │ │ │ -B8BD7 Filename Length 0015 (21) │ │ │ │ -B8BD9 Extra Length 0018 (24) │ │ │ │ -B8BDB Comment Length 0000 (0) │ │ │ │ -B8BDD Disk Start 0000 (0) │ │ │ │ -B8BDF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8BE1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8BE5 Local Header Offset 00017DA1 (97697) │ │ │ │ -B8BE9 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8BE9: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8BFE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8C00 Length 0005 (5) │ │ │ │ -B8C02 Flags 01 (1) 'Modification' │ │ │ │ -B8C03 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8C07 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8C09 Length 000B (11) │ │ │ │ -B8C0B Version 01 (1) │ │ │ │ -B8C0C UID Size 04 (4) │ │ │ │ -B8C0D UID 00000000 (0) │ │ │ │ -B8C11 GID Size 04 (4) │ │ │ │ -B8C12 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8C16 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -B8C1A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8C1B Created OS 03 (3) 'Unix' │ │ │ │ -B8C1C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8C1D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8C1E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8C20 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8C22 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8C26 CRC 3EF52A66 (1056254566) │ │ │ │ -B8C2A Compressed Size 000091A7 (37287) │ │ │ │ -B8C2E Uncompressed Size 0003D5E7 (251367) │ │ │ │ -B8C32 Filename Length 0014 (20) │ │ │ │ -B8C34 Extra Length 0018 (24) │ │ │ │ -B8C36 Comment Length 0000 (0) │ │ │ │ -B8C38 Disk Start 0000 (0) │ │ │ │ -B8C3A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8C3C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8C40 Local Header Offset 0001B8FA (112890) │ │ │ │ -B8C44 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8C44: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8C58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8C5A Length 0005 (5) │ │ │ │ -B8C5C Flags 01 (1) 'Modification' │ │ │ │ -B8C5D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8C61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8C63 Length 000B (11) │ │ │ │ -B8C65 Version 01 (1) │ │ │ │ -B8C66 UID Size 04 (4) │ │ │ │ -B8C67 UID 00000000 (0) │ │ │ │ -B8C6B GID Size 04 (4) │ │ │ │ -B8C6C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8C70 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -B8C74 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8C75 Created OS 03 (3) 'Unix' │ │ │ │ -B8C76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8C77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8C78 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8C7A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8C7C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8C80 CRC 3A7A1761 (981079905) │ │ │ │ -B8C84 Compressed Size 00001217 (4631) │ │ │ │ -B8C88 Uncompressed Size 00003C8F (15503) │ │ │ │ -B8C8C Filename Length 0010 (16) │ │ │ │ -B8C8E Extra Length 0018 (24) │ │ │ │ -B8C90 Comment Length 0000 (0) │ │ │ │ -B8C92 Disk Start 0000 (0) │ │ │ │ -B8C94 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8C96 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8C9A Local Header Offset 00024AEF (150255) │ │ │ │ -B8C9E Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8C9E: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8CAE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8CB0 Length 0005 (5) │ │ │ │ -B8CB2 Flags 01 (1) 'Modification' │ │ │ │ -B8CB3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8CB7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8CB9 Length 000B (11) │ │ │ │ -B8CBB Version 01 (1) │ │ │ │ -B8CBC UID Size 04 (4) │ │ │ │ -B8CBD UID 00000000 (0) │ │ │ │ -B8CC1 GID Size 04 (4) │ │ │ │ -B8CC2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8CC6 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -B8CCA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8CCB Created OS 03 (3) 'Unix' │ │ │ │ -B8CCC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8CCD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8CCE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8CD0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8CD2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8CD6 CRC 0B910DE0 (194055648) │ │ │ │ -B8CDA Compressed Size 00002A72 (10866) │ │ │ │ -B8CDE Uncompressed Size 0001151D (70941) │ │ │ │ -B8CE2 Filename Length 0016 (22) │ │ │ │ -B8CE4 Extra Length 0018 (24) │ │ │ │ -B8CE6 Comment Length 0000 (0) │ │ │ │ -B8CE8 Disk Start 0000 (0) │ │ │ │ -B8CEA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8CEC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8CF0 Local Header Offset 00025D50 (154960) │ │ │ │ -B8CF4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8CF4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8D0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8D0C Length 0005 (5) │ │ │ │ -B8D0E Flags 01 (1) 'Modification' │ │ │ │ -B8D0F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8D13 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8D15 Length 000B (11) │ │ │ │ -B8D17 Version 01 (1) │ │ │ │ -B8D18 UID Size 04 (4) │ │ │ │ -B8D19 UID 00000000 (0) │ │ │ │ -B8D1D GID Size 04 (4) │ │ │ │ -B8D1E GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8D22 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -B8D26 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8D27 Created OS 03 (3) 'Unix' │ │ │ │ -B8D28 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8D29 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8D2A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8D2C Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8D2E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8D32 CRC 1DBD56C5 (498947781) │ │ │ │ -B8D36 Compressed Size 000014D7 (5335) │ │ │ │ -B8D3A Uncompressed Size 0000518B (20875) │ │ │ │ -B8D3E Filename Length 001D (29) │ │ │ │ -B8D40 Extra Length 0018 (24) │ │ │ │ -B8D42 Comment Length 0000 (0) │ │ │ │ -B8D44 Disk Start 0000 (0) │ │ │ │ -B8D46 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8D48 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8D4C Local Header Offset 00028812 (165906) │ │ │ │ -B8D50 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8D50: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8D6D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8D6F Length 0005 (5) │ │ │ │ -B8D71 Flags 01 (1) 'Modification' │ │ │ │ -B8D72 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8D76 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8D78 Length 000B (11) │ │ │ │ -B8D7A Version 01 (1) │ │ │ │ -B8D7B UID Size 04 (4) │ │ │ │ -B8D7C UID 00000000 (0) │ │ │ │ -B8D80 GID Size 04 (4) │ │ │ │ -B8D81 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8D85 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -B8D89 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8D8A Created OS 03 (3) 'Unix' │ │ │ │ -B8D8B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8D8C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8D8D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8D8F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8D91 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8D95 CRC 3C39EBFA (1010428922) │ │ │ │ -B8D99 Compressed Size 00003805 (14341) │ │ │ │ -B8D9D Uncompressed Size 0000EA4A (59978) │ │ │ │ -B8DA1 Filename Length 001C (28) │ │ │ │ -B8DA3 Extra Length 0018 (24) │ │ │ │ -B8DA5 Comment Length 0000 (0) │ │ │ │ -B8DA7 Disk Start 0000 (0) │ │ │ │ -B8DA9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8DAB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8DAF Local Header Offset 00029D40 (171328) │ │ │ │ -B8DB3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8DB3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8DCF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8DD1 Length 0005 (5) │ │ │ │ -B8DD3 Flags 01 (1) 'Modification' │ │ │ │ -B8DD4 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8DD8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8DDA Length 000B (11) │ │ │ │ -B8DDC Version 01 (1) │ │ │ │ -B8DDD UID Size 04 (4) │ │ │ │ -B8DDE UID 00000000 (0) │ │ │ │ -B8DE2 GID Size 04 (4) │ │ │ │ -B8DE3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8DE7 CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -B8DEB Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8DEC Created OS 03 (3) 'Unix' │ │ │ │ -B8DED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8DEE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8DEF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8DF1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8DF3 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8DF7 CRC 9B224725 (2602714917) │ │ │ │ -B8DFB Compressed Size 0000069E (1694) │ │ │ │ -B8DFF Uncompressed Size 000011F2 (4594) │ │ │ │ -B8E03 Filename Length 001C (28) │ │ │ │ -B8E05 Extra Length 0018 (24) │ │ │ │ -B8E07 Comment Length 0000 (0) │ │ │ │ -B8E09 Disk Start 0000 (0) │ │ │ │ -B8E0B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8E0D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8E11 Local Header Offset 0002D59B (185755) │ │ │ │ -B8E15 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8E15: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8E31 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8E33 Length 0005 (5) │ │ │ │ -B8E35 Flags 01 (1) 'Modification' │ │ │ │ -B8E36 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8E3A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8E3C Length 000B (11) │ │ │ │ -B8E3E Version 01 (1) │ │ │ │ -B8E3F UID Size 04 (4) │ │ │ │ -B8E40 UID 00000000 (0) │ │ │ │ -B8E44 GID Size 04 (4) │ │ │ │ -B8E45 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8E49 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -B8E4D Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8E4E Created OS 03 (3) 'Unix' │ │ │ │ -B8E4F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8E50 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8E51 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8E53 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8E55 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8E59 CRC D43062FF (3559940863) │ │ │ │ -B8E5D Compressed Size 0000107A (4218) │ │ │ │ -B8E61 Uncompressed Size 00004BFD (19453) │ │ │ │ -B8E65 Filename Length 001B (27) │ │ │ │ -B8E67 Extra Length 0018 (24) │ │ │ │ -B8E69 Comment Length 0000 (0) │ │ │ │ -B8E6B Disk Start 0000 (0) │ │ │ │ -B8E6D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8E6F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8E73 Local Header Offset 0002DC8F (187535) │ │ │ │ -B8E77 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8E77: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8E92 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8E94 Length 0005 (5) │ │ │ │ -B8E96 Flags 01 (1) 'Modification' │ │ │ │ -B8E97 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8E9B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8E9D Length 000B (11) │ │ │ │ -B8E9F Version 01 (1) │ │ │ │ -B8EA0 UID Size 04 (4) │ │ │ │ -B8EA1 UID 00000000 (0) │ │ │ │ -B8EA5 GID Size 04 (4) │ │ │ │ -B8EA6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8EAA CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -B8EAE Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8EAF Created OS 03 (3) 'Unix' │ │ │ │ -B8EB0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8EB1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8EB2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8EB4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8EB6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8EBA CRC 62BD2C80 (1656564864) │ │ │ │ -B8EBE Compressed Size 00003BE0 (15328) │ │ │ │ -B8EC2 Uncompressed Size 0000D781 (55169) │ │ │ │ -B8EC6 Filename Length 001D (29) │ │ │ │ -B8EC8 Extra Length 0018 (24) │ │ │ │ -B8ECA Comment Length 0000 (0) │ │ │ │ -B8ECC Disk Start 0000 (0) │ │ │ │ -B8ECE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8ED0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8ED4 Local Header Offset 0002ED5E (191838) │ │ │ │ -B8ED8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8ED8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8EF5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8EF7 Length 0005 (5) │ │ │ │ -B8EF9 Flags 01 (1) 'Modification' │ │ │ │ -B8EFA Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8EFE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8F00 Length 000B (11) │ │ │ │ -B8F02 Version 01 (1) │ │ │ │ -B8F03 UID Size 04 (4) │ │ │ │ -B8F04 UID 00000000 (0) │ │ │ │ -B8F08 GID Size 04 (4) │ │ │ │ -B8F09 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8F0D CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -B8F11 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8F12 Created OS 03 (3) 'Unix' │ │ │ │ -B8F13 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8F14 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8F15 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8F17 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8F19 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8F1D CRC E058F5F9 (3763926521) │ │ │ │ -B8F21 Compressed Size 00000D67 (3431) │ │ │ │ -B8F25 Uncompressed Size 0000388B (14475) │ │ │ │ -B8F29 Filename Length 001D (29) │ │ │ │ -B8F2B Extra Length 0018 (24) │ │ │ │ -B8F2D Comment Length 0000 (0) │ │ │ │ -B8F2F Disk Start 0000 (0) │ │ │ │ -B8F31 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8F33 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8F37 Local Header Offset 00032995 (207253) │ │ │ │ -B8F3B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8F3B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8F58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8F5A Length 0005 (5) │ │ │ │ -B8F5C Flags 01 (1) 'Modification' │ │ │ │ -B8F5D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8F61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8F63 Length 000B (11) │ │ │ │ -B8F65 Version 01 (1) │ │ │ │ -B8F66 UID Size 04 (4) │ │ │ │ -B8F67 UID 00000000 (0) │ │ │ │ -B8F6B GID Size 04 (4) │ │ │ │ -B8F6C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8F70 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -B8F74 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8F75 Created OS 03 (3) 'Unix' │ │ │ │ -B8F76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8F77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8F78 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8F7A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8F7C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8F80 CRC 142A122B (338301483) │ │ │ │ -B8F84 Compressed Size 00001C66 (7270) │ │ │ │ -B8F88 Uncompressed Size 0000C184 (49540) │ │ │ │ -B8F8C Filename Length 001A (26) │ │ │ │ -B8F8E Extra Length 0018 (24) │ │ │ │ -B8F90 Comment Length 0000 (0) │ │ │ │ -B8F92 Disk Start 0000 (0) │ │ │ │ -B8F94 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8F96 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8F9A Local Header Offset 00033753 (210771) │ │ │ │ -B8F9E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8F9E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8FB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8FBA Length 0005 (5) │ │ │ │ -B8FBC Flags 01 (1) 'Modification' │ │ │ │ -B8FBD Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B8FC1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8FC3 Length 000B (11) │ │ │ │ -B8FC5 Version 01 (1) │ │ │ │ -B8FC6 UID Size 04 (4) │ │ │ │ -B8FC7 UID 00000000 (0) │ │ │ │ -B8FCB GID Size 04 (4) │ │ │ │ -B8FCC GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8FD0 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -B8FD4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8FD5 Created OS 03 (3) 'Unix' │ │ │ │ -B8FD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8FD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8FD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8FDA Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8FDC Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B8FE0 CRC 2BF4762B (737441323) │ │ │ │ -B8FE4 Compressed Size 000003DE (990) │ │ │ │ -B8FE8 Uncompressed Size 00000933 (2355) │ │ │ │ -B8FEC Filename Length 0012 (18) │ │ │ │ -B8FEE Extra Length 0018 (24) │ │ │ │ -B8FF0 Comment Length 0000 (0) │ │ │ │ -B8FF2 Disk Start 0000 (0) │ │ │ │ -B8FF4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8FF6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8FFA Local Header Offset 0003540D (218125) │ │ │ │ -B8FFE Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8FFE: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9010 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9012 Length 0005 (5) │ │ │ │ -B9014 Flags 01 (1) 'Modification' │ │ │ │ -B9015 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9019 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B901B Length 000B (11) │ │ │ │ -B901D Version 01 (1) │ │ │ │ -B901E UID Size 04 (4) │ │ │ │ -B901F UID 00000000 (0) │ │ │ │ -B9023 GID Size 04 (4) │ │ │ │ -B9024 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9028 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -B902C Created Zip Spec 3D (61) '6.1' │ │ │ │ -B902D Created OS 03 (3) 'Unix' │ │ │ │ -B902E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B902F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9030 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9032 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9034 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9038 CRC A4B161AE (2763088302) │ │ │ │ -B903C Compressed Size 000001D1 (465) │ │ │ │ -B9040 Uncompressed Size 0000030F (783) │ │ │ │ -B9044 Filename Length 0020 (32) │ │ │ │ -B9046 Extra Length 0018 (24) │ │ │ │ -B9048 Comment Length 0000 (0) │ │ │ │ -B904A Disk Start 0000 (0) │ │ │ │ -B904C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B904E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9052 Local Header Offset 00035837 (219191) │ │ │ │ -B9056 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9056: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9076 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9078 Length 0005 (5) │ │ │ │ -B907A Flags 01 (1) 'Modification' │ │ │ │ -B907B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B907F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9081 Length 000B (11) │ │ │ │ -B9083 Version 01 (1) │ │ │ │ -B9084 UID Size 04 (4) │ │ │ │ -B9085 UID 00000000 (0) │ │ │ │ -B9089 GID Size 04 (4) │ │ │ │ -B908A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B908E CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -B9092 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9093 Created OS 03 (3) 'Unix' │ │ │ │ -B9094 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9095 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9096 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9098 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B909A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B909E CRC 4DE68BB8 (1306954680) │ │ │ │ -B90A2 Compressed Size 000017A6 (6054) │ │ │ │ -B90A6 Uncompressed Size 00009D16 (40214) │ │ │ │ -B90AA Filename Length 001B (27) │ │ │ │ -B90AC Extra Length 0018 (24) │ │ │ │ -B90AE Comment Length 0000 (0) │ │ │ │ -B90B0 Disk Start 0000 (0) │ │ │ │ -B90B2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B90B4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B90B8 Local Header Offset 00035A62 (219746) │ │ │ │ -B90BC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB90BC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B90D7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B90D9 Length 0005 (5) │ │ │ │ -B90DB Flags 01 (1) 'Modification' │ │ │ │ -B90DC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B90E0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B90E2 Length 000B (11) │ │ │ │ -B90E4 Version 01 (1) │ │ │ │ -B90E5 UID Size 04 (4) │ │ │ │ -B90E6 UID 00000000 (0) │ │ │ │ -B90EA GID Size 04 (4) │ │ │ │ -B90EB GID 00000000 (0) │ │ │ │ - │ │ │ │ -B90EF CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -B90F3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B90F4 Created OS 03 (3) 'Unix' │ │ │ │ -B90F5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B90F6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B90F7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B90F9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B90FB Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B90FF CRC 1F5C05CF (526124495) │ │ │ │ -B9103 Compressed Size 00001370 (4976) │ │ │ │ -B9107 Uncompressed Size 00003B5F (15199) │ │ │ │ -B910B Filename Length 0015 (21) │ │ │ │ -B910D Extra Length 0018 (24) │ │ │ │ -B910F Comment Length 0000 (0) │ │ │ │ -B9111 Disk Start 0000 (0) │ │ │ │ -B9113 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9115 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9119 Local Header Offset 0003725D (225885) │ │ │ │ -B911D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB911D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9132 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9134 Length 0005 (5) │ │ │ │ -B9136 Flags 01 (1) 'Modification' │ │ │ │ -B9137 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B913B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B913D Length 000B (11) │ │ │ │ -B913F Version 01 (1) │ │ │ │ -B9140 UID Size 04 (4) │ │ │ │ -B9141 UID 00000000 (0) │ │ │ │ -B9145 GID Size 04 (4) │ │ │ │ -B9146 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B914A CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -B914E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B914F Created OS 03 (3) 'Unix' │ │ │ │ -B9150 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9151 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9152 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9154 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9156 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B915A CRC ED1A23A7 (3977913255) │ │ │ │ -B915E Compressed Size 00000ACC (2764) │ │ │ │ -B9162 Uncompressed Size 00002133 (8499) │ │ │ │ -B9166 Filename Length 0011 (17) │ │ │ │ -B9168 Extra Length 0018 (24) │ │ │ │ -B916A Comment Length 0000 (0) │ │ │ │ -B916C Disk Start 0000 (0) │ │ │ │ -B916E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9170 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9174 Local Header Offset 0003861C (230940) │ │ │ │ -B9178 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9178: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9189 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B918B Length 0005 (5) │ │ │ │ -B918D Flags 01 (1) 'Modification' │ │ │ │ -B918E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9192 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9194 Length 000B (11) │ │ │ │ -B9196 Version 01 (1) │ │ │ │ -B9197 UID Size 04 (4) │ │ │ │ -B9198 UID 00000000 (0) │ │ │ │ -B919C GID Size 04 (4) │ │ │ │ -B919D GID 00000000 (0) │ │ │ │ - │ │ │ │ -B91A1 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -B91A5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B91A6 Created OS 03 (3) 'Unix' │ │ │ │ -B91A7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B91A8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B91A9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B91AB Compression Method 0008 (8) 'Deflated' │ │ │ │ -B91AD Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B91B1 CRC 8C273D06 (2351381766) │ │ │ │ -B91B5 Compressed Size 000003FD (1021) │ │ │ │ -B91B9 Uncompressed Size 00000F0A (3850) │ │ │ │ -B91BD Filename Length 0014 (20) │ │ │ │ -B91BF Extra Length 0018 (24) │ │ │ │ -B91C1 Comment Length 0000 (0) │ │ │ │ -B91C3 Disk Start 0000 (0) │ │ │ │ -B91C5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B91C7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B91CB Local Header Offset 00039133 (233779) │ │ │ │ -B91CF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB91CF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B91E3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B91E5 Length 0005 (5) │ │ │ │ -B91E7 Flags 01 (1) 'Modification' │ │ │ │ -B91E8 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B91EC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B91EE Length 000B (11) │ │ │ │ -B91F0 Version 01 (1) │ │ │ │ -B91F1 UID Size 04 (4) │ │ │ │ -B91F2 UID 00000000 (0) │ │ │ │ -B91F6 GID Size 04 (4) │ │ │ │ -B91F7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B91FB CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -B91FF Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9200 Created OS 03 (3) 'Unix' │ │ │ │ -B9201 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9202 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9203 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9205 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9207 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B920B CRC A1F8FAEC (2717448940) │ │ │ │ -B920F Compressed Size 0000125F (4703) │ │ │ │ -B9213 Uncompressed Size 00003467 (13415) │ │ │ │ -B9217 Filename Length 0014 (20) │ │ │ │ -B9219 Extra Length 0018 (24) │ │ │ │ -B921B Comment Length 0000 (0) │ │ │ │ -B921D Disk Start 0000 (0) │ │ │ │ -B921F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9221 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9225 Local Header Offset 0003957E (234878) │ │ │ │ -B9229 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9229: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B923D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B923F Length 0005 (5) │ │ │ │ -B9241 Flags 01 (1) 'Modification' │ │ │ │ -B9242 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9246 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9248 Length 000B (11) │ │ │ │ -B924A Version 01 (1) │ │ │ │ -B924B UID Size 04 (4) │ │ │ │ -B924C UID 00000000 (0) │ │ │ │ -B9250 GID Size 04 (4) │ │ │ │ -B9251 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9255 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -B9259 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B925A Created OS 03 (3) 'Unix' │ │ │ │ -B925B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B925C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B925D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B925F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9261 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9265 CRC 089B1E28 (144383528) │ │ │ │ -B9269 Compressed Size 00000ACD (2765) │ │ │ │ -B926D Uncompressed Size 000022FD (8957) │ │ │ │ -B9271 Filename Length 001B (27) │ │ │ │ -B9273 Extra Length 0018 (24) │ │ │ │ -B9275 Comment Length 0000 (0) │ │ │ │ -B9277 Disk Start 0000 (0) │ │ │ │ -B9279 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B927B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B927F Local Header Offset 0003A82B (239659) │ │ │ │ -B9283 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9283: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B929E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B92A0 Length 0005 (5) │ │ │ │ -B92A2 Flags 01 (1) 'Modification' │ │ │ │ -B92A3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B92A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B92A9 Length 000B (11) │ │ │ │ -B92AB Version 01 (1) │ │ │ │ -B92AC UID Size 04 (4) │ │ │ │ -B92AD UID 00000000 (0) │ │ │ │ -B92B1 GID Size 04 (4) │ │ │ │ -B92B2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B92B6 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -B92BA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B92BB Created OS 03 (3) 'Unix' │ │ │ │ -B92BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B92BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B92BE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B92C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B92C2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B92C6 CRC 97366AE5 (2536925925) │ │ │ │ -B92CA Compressed Size 00000C4F (3151) │ │ │ │ -B92CE Uncompressed Size 0000273A (10042) │ │ │ │ -B92D2 Filename Length 0013 (19) │ │ │ │ -B92D4 Extra Length 0018 (24) │ │ │ │ -B92D6 Comment Length 0000 (0) │ │ │ │ -B92D8 Disk Start 0000 (0) │ │ │ │ -B92DA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B92DC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B92E0 Local Header Offset 0003B34D (242509) │ │ │ │ -B92E4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB92E4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B92F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B92F9 Length 0005 (5) │ │ │ │ -B92FB Flags 01 (1) 'Modification' │ │ │ │ -B92FC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9300 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9302 Length 000B (11) │ │ │ │ -B9304 Version 01 (1) │ │ │ │ -B9305 UID Size 04 (4) │ │ │ │ -B9306 UID 00000000 (0) │ │ │ │ -B930A GID Size 04 (4) │ │ │ │ -B930B GID 00000000 (0) │ │ │ │ - │ │ │ │ -B930F CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -B9313 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9314 Created OS 03 (3) 'Unix' │ │ │ │ -B9315 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9316 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9317 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9319 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B931B Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B931F CRC 275B1E57 (660282967) │ │ │ │ -B9323 Compressed Size 00000C92 (3218) │ │ │ │ -B9327 Uncompressed Size 00003D0E (15630) │ │ │ │ -B932B Filename Length 0014 (20) │ │ │ │ -B932D Extra Length 0018 (24) │ │ │ │ -B932F Comment Length 0000 (0) │ │ │ │ -B9331 Disk Start 0000 (0) │ │ │ │ -B9333 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9335 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9339 Local Header Offset 0003BFE9 (245737) │ │ │ │ -B933D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB933D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9351 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9353 Length 0005 (5) │ │ │ │ -B9355 Flags 01 (1) 'Modification' │ │ │ │ -B9356 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B935A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B935C Length 000B (11) │ │ │ │ -B935E Version 01 (1) │ │ │ │ -B935F UID Size 04 (4) │ │ │ │ -B9360 UID 00000000 (0) │ │ │ │ -B9364 GID Size 04 (4) │ │ │ │ -B9365 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9369 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -B936D Created Zip Spec 3D (61) '6.1' │ │ │ │ -B936E Created OS 03 (3) 'Unix' │ │ │ │ -B936F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9370 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9371 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9373 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9375 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9379 CRC 7A7EC40C (2055128076) │ │ │ │ -B937D Compressed Size 00000F44 (3908) │ │ │ │ -B9381 Uncompressed Size 00003742 (14146) │ │ │ │ -B9385 Filename Length 000F (15) │ │ │ │ -B9387 Extra Length 0018 (24) │ │ │ │ -B9389 Comment Length 0000 (0) │ │ │ │ -B938B Disk Start 0000 (0) │ │ │ │ -B938D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B938F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9393 Local Header Offset 0003CCC9 (249033) │ │ │ │ -B9397 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9397: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B93A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B93A8 Length 0005 (5) │ │ │ │ -B93AA Flags 01 (1) 'Modification' │ │ │ │ -B93AB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B93AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B93B1 Length 000B (11) │ │ │ │ -B93B3 Version 01 (1) │ │ │ │ -B93B4 UID Size 04 (4) │ │ │ │ -B93B5 UID 00000000 (0) │ │ │ │ -B93B9 GID Size 04 (4) │ │ │ │ -B93BA GID 00000000 (0) │ │ │ │ - │ │ │ │ -B93BE CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -B93C2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B93C3 Created OS 03 (3) 'Unix' │ │ │ │ -B93C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B93C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B93C6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B93C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B93CA Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B93CE CRC 5282A93D (1384294717) │ │ │ │ -B93D2 Compressed Size 000006B9 (1721) │ │ │ │ -B93D6 Uncompressed Size 00001A77 (6775) │ │ │ │ -B93DA Filename Length 000F (15) │ │ │ │ -B93DC Extra Length 0018 (24) │ │ │ │ -B93DE Comment Length 0000 (0) │ │ │ │ -B93E0 Disk Start 0000 (0) │ │ │ │ -B93E2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B93E4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B93E8 Local Header Offset 0003DC56 (253014) │ │ │ │ -B93EC Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB93EC: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B93FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B93FD Length 0005 (5) │ │ │ │ -B93FF Flags 01 (1) 'Modification' │ │ │ │ -B9400 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9404 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9406 Length 000B (11) │ │ │ │ -B9408 Version 01 (1) │ │ │ │ -B9409 UID Size 04 (4) │ │ │ │ -B940A UID 00000000 (0) │ │ │ │ -B940E GID Size 04 (4) │ │ │ │ -B940F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9413 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -B9417 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9418 Created OS 03 (3) 'Unix' │ │ │ │ -B9419 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B941A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B941B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B941D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B941F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9423 CRC 74F46BE4 (1962175460) │ │ │ │ -B9427 Compressed Size 00001A41 (6721) │ │ │ │ -B942B Uncompressed Size 000064F8 (25848) │ │ │ │ -B942F Filename Length 0013 (19) │ │ │ │ -B9431 Extra Length 0018 (24) │ │ │ │ -B9433 Comment Length 0000 (0) │ │ │ │ -B9435 Disk Start 0000 (0) │ │ │ │ -B9437 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9439 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B943D Local Header Offset 0003E358 (254808) │ │ │ │ -B9441 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9441: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9454 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9456 Length 0005 (5) │ │ │ │ -B9458 Flags 01 (1) 'Modification' │ │ │ │ -B9459 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B945D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B945F Length 000B (11) │ │ │ │ -B9461 Version 01 (1) │ │ │ │ -B9462 UID Size 04 (4) │ │ │ │ -B9463 UID 00000000 (0) │ │ │ │ -B9467 GID Size 04 (4) │ │ │ │ -B9468 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B946C CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -B9470 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9471 Created OS 03 (3) 'Unix' │ │ │ │ -B9472 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9473 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9474 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9476 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9478 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B947C CRC 5D9A5F08 (1570397960) │ │ │ │ -B9480 Compressed Size 000009A4 (2468) │ │ │ │ -B9484 Uncompressed Size 00001B62 (7010) │ │ │ │ -B9488 Filename Length 0010 (16) │ │ │ │ -B948A Extra Length 0018 (24) │ │ │ │ -B948C Comment Length 0000 (0) │ │ │ │ -B948E Disk Start 0000 (0) │ │ │ │ -B9490 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9492 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9496 Local Header Offset 0003FDE6 (261606) │ │ │ │ -B949A Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB949A: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B94AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B94AC Length 0005 (5) │ │ │ │ -B94AE Flags 01 (1) 'Modification' │ │ │ │ -B94AF Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B94B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B94B5 Length 000B (11) │ │ │ │ -B94B7 Version 01 (1) │ │ │ │ -B94B8 UID Size 04 (4) │ │ │ │ -B94B9 UID 00000000 (0) │ │ │ │ -B94BD GID Size 04 (4) │ │ │ │ -B94BE GID 00000000 (0) │ │ │ │ - │ │ │ │ -B94C2 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -B94C6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B94C7 Created OS 03 (3) 'Unix' │ │ │ │ -B94C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B94C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B94CA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B94CC Compression Method 0008 (8) 'Deflated' │ │ │ │ -B94CE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B94D2 CRC 2F07E577 (789046647) │ │ │ │ -B94D6 Compressed Size 000006B5 (1717) │ │ │ │ -B94DA Uncompressed Size 00001563 (5475) │ │ │ │ -B94DE Filename Length 0012 (18) │ │ │ │ -B94E0 Extra Length 0018 (24) │ │ │ │ -B94E2 Comment Length 0000 (0) │ │ │ │ -B94E4 Disk Start 0000 (0) │ │ │ │ -B94E6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B94E8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B94EC Local Header Offset 000407D4 (264148) │ │ │ │ -B94F0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB94F0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9502 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9504 Length 0005 (5) │ │ │ │ -B9506 Flags 01 (1) 'Modification' │ │ │ │ -B9507 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B950B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B950D Length 000B (11) │ │ │ │ -B950F Version 01 (1) │ │ │ │ -B9510 UID Size 04 (4) │ │ │ │ -B9511 UID 00000000 (0) │ │ │ │ -B9515 GID Size 04 (4) │ │ │ │ -B9516 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B951A CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -B951E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B951F Created OS 03 (3) 'Unix' │ │ │ │ -B9520 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9521 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9522 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9524 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9526 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B952A CRC 57512FDB (1464938459) │ │ │ │ -B952E Compressed Size 00002D61 (11617) │ │ │ │ -B9532 Uncompressed Size 0000D07C (53372) │ │ │ │ -B9536 Filename Length 0010 (16) │ │ │ │ -B9538 Extra Length 0018 (24) │ │ │ │ -B953A Comment Length 0000 (0) │ │ │ │ -B953C Disk Start 0000 (0) │ │ │ │ -B953E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9540 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9544 Local Header Offset 00040ED5 (265941) │ │ │ │ -B9548 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9548: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9558 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B955A Length 0005 (5) │ │ │ │ -B955C Flags 01 (1) 'Modification' │ │ │ │ -B955D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9561 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9563 Length 000B (11) │ │ │ │ -B9565 Version 01 (1) │ │ │ │ -B9566 UID Size 04 (4) │ │ │ │ -B9567 UID 00000000 (0) │ │ │ │ -B956B GID Size 04 (4) │ │ │ │ -B956C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9570 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -B9574 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9575 Created OS 03 (3) 'Unix' │ │ │ │ -B9576 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9577 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9578 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B957A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B957C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9580 CRC 0F5DE7B2 (257812402) │ │ │ │ -B9584 Compressed Size 00001E87 (7815) │ │ │ │ -B9588 Uncompressed Size 00009AA8 (39592) │ │ │ │ -B958C Filename Length 0012 (18) │ │ │ │ -B958E Extra Length 0018 (24) │ │ │ │ -B9590 Comment Length 0000 (0) │ │ │ │ -B9592 Disk Start 0000 (0) │ │ │ │ -B9594 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9596 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B959A Local Header Offset 00043C80 (277632) │ │ │ │ -B959E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB959E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B95B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B95B2 Length 0005 (5) │ │ │ │ -B95B4 Flags 01 (1) 'Modification' │ │ │ │ -B95B5 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B95B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B95BB Length 000B (11) │ │ │ │ -B95BD Version 01 (1) │ │ │ │ -B95BE UID Size 04 (4) │ │ │ │ -B95BF UID 00000000 (0) │ │ │ │ -B95C3 GID Size 04 (4) │ │ │ │ -B95C4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B95C8 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -B95CC Created Zip Spec 3D (61) '6.1' │ │ │ │ -B95CD Created OS 03 (3) 'Unix' │ │ │ │ -B95CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B95CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B95D0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B95D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B95D4 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B95D8 CRC B2A9F717 (2997483287) │ │ │ │ -B95DC Compressed Size 00001471 (5233) │ │ │ │ -B95E0 Uncompressed Size 00007ACD (31437) │ │ │ │ -B95E4 Filename Length 0018 (24) │ │ │ │ -B95E6 Extra Length 0018 (24) │ │ │ │ -B95E8 Comment Length 0000 (0) │ │ │ │ -B95EA Disk Start 0000 (0) │ │ │ │ -B95EC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B95EE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B95F2 Local Header Offset 00045B53 (285523) │ │ │ │ -B95F6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB95F6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B960E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9610 Length 0005 (5) │ │ │ │ -B9612 Flags 01 (1) 'Modification' │ │ │ │ -B9613 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9617 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9619 Length 000B (11) │ │ │ │ -B961B Version 01 (1) │ │ │ │ -B961C UID Size 04 (4) │ │ │ │ -B961D UID 00000000 (0) │ │ │ │ -B9621 GID Size 04 (4) │ │ │ │ -B9622 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9626 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -B962A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B962B Created OS 03 (3) 'Unix' │ │ │ │ -B962C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B962D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B962E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9630 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9632 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9636 CRC 12B9321E (314126878) │ │ │ │ -B963A Compressed Size 00002147 (8519) │ │ │ │ -B963E Uncompressed Size 0000CCBC (52412) │ │ │ │ -B9642 Filename Length 001F (31) │ │ │ │ -B9644 Extra Length 0018 (24) │ │ │ │ -B9646 Comment Length 0000 (0) │ │ │ │ -B9648 Disk Start 0000 (0) │ │ │ │ -B964A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B964C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9650 Local Header Offset 00047016 (290838) │ │ │ │ -B9654 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9654: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9673 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9675 Length 0005 (5) │ │ │ │ -B9677 Flags 01 (1) 'Modification' │ │ │ │ -B9678 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B967C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B967E Length 000B (11) │ │ │ │ -B9680 Version 01 (1) │ │ │ │ -B9681 UID Size 04 (4) │ │ │ │ -B9682 UID 00000000 (0) │ │ │ │ -B9686 GID Size 04 (4) │ │ │ │ -B9687 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B968B CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -B968F Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9690 Created OS 03 (3) 'Unix' │ │ │ │ -B9691 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9692 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9693 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9695 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9697 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B969B CRC 80CE55AF (2161005999) │ │ │ │ -B969F Compressed Size 000003F5 (1013) │ │ │ │ -B96A3 Uncompressed Size 000008A1 (2209) │ │ │ │ -B96A7 Filename Length 001E (30) │ │ │ │ -B96A9 Extra Length 0018 (24) │ │ │ │ -B96AB Comment Length 0000 (0) │ │ │ │ -B96AD Disk Start 0000 (0) │ │ │ │ -B96AF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B96B1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B96B5 Local Header Offset 000491B6 (299446) │ │ │ │ -B96B9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB96B9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B96D7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B96D9 Length 0005 (5) │ │ │ │ -B96DB Flags 01 (1) 'Modification' │ │ │ │ -B96DC Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B96E0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B96E2 Length 000B (11) │ │ │ │ -B96E4 Version 01 (1) │ │ │ │ -B96E5 UID Size 04 (4) │ │ │ │ -B96E6 UID 00000000 (0) │ │ │ │ -B96EA GID Size 04 (4) │ │ │ │ -B96EB GID 00000000 (0) │ │ │ │ - │ │ │ │ -B96EF CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -B96F3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B96F4 Created OS 03 (3) 'Unix' │ │ │ │ -B96F5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B96F6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B96F7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B96F9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B96FB Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B96FF CRC DF319C26 (3744570406) │ │ │ │ -B9703 Compressed Size 00004308 (17160) │ │ │ │ -B9707 Uncompressed Size 0000DAC9 (56009) │ │ │ │ -B970B Filename Length 0013 (19) │ │ │ │ -B970D Extra Length 0018 (24) │ │ │ │ -B970F Comment Length 0000 (0) │ │ │ │ -B9711 Disk Start 0000 (0) │ │ │ │ -B9713 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9715 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9719 Local Header Offset 00049603 (300547) │ │ │ │ -B971D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB971D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9730 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9732 Length 0005 (5) │ │ │ │ -B9734 Flags 01 (1) 'Modification' │ │ │ │ -B9735 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9739 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B973B Length 000B (11) │ │ │ │ -B973D Version 01 (1) │ │ │ │ -B973E UID Size 04 (4) │ │ │ │ -B973F UID 00000000 (0) │ │ │ │ -B9743 GID Size 04 (4) │ │ │ │ -B9744 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9748 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -B974C Created Zip Spec 3D (61) '6.1' │ │ │ │ -B974D Created OS 03 (3) 'Unix' │ │ │ │ -B974E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B974F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9750 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9752 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9754 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9758 CRC 571A6F3A (1461350202) │ │ │ │ -B975C Compressed Size 000026C1 (9921) │ │ │ │ -B9760 Uncompressed Size 00006E43 (28227) │ │ │ │ -B9764 Filename Length 0019 (25) │ │ │ │ -B9766 Extra Length 0018 (24) │ │ │ │ -B9768 Comment Length 0000 (0) │ │ │ │ -B976A Disk Start 0000 (0) │ │ │ │ -B976C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B976E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9772 Local Header Offset 0004D958 (317784) │ │ │ │ -B9776 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9776: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B978F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9791 Length 0005 (5) │ │ │ │ -B9793 Flags 01 (1) 'Modification' │ │ │ │ -B9794 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9798 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B979A Length 000B (11) │ │ │ │ -B979C Version 01 (1) │ │ │ │ -B979D UID Size 04 (4) │ │ │ │ -B979E UID 00000000 (0) │ │ │ │ -B97A2 GID Size 04 (4) │ │ │ │ -B97A3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B97A7 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -B97AB Created Zip Spec 3D (61) '6.1' │ │ │ │ -B97AC Created OS 03 (3) 'Unix' │ │ │ │ -B97AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B97AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B97AF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B97B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B97B3 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B97B7 CRC C06E9EF2 (3228475122) │ │ │ │ -B97BB Compressed Size 0000273D (10045) │ │ │ │ -B97BF Uncompressed Size 00008B81 (35713) │ │ │ │ -B97C3 Filename Length 0019 (25) │ │ │ │ -B97C5 Extra Length 0018 (24) │ │ │ │ -B97C7 Comment Length 0000 (0) │ │ │ │ -B97C9 Disk Start 0000 (0) │ │ │ │ -B97CB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B97CD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B97D1 Local Header Offset 0005006C (327788) │ │ │ │ -B97D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB97D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B97EE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B97F0 Length 0005 (5) │ │ │ │ -B97F2 Flags 01 (1) 'Modification' │ │ │ │ -B97F3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B97F7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B97F9 Length 000B (11) │ │ │ │ -B97FB Version 01 (1) │ │ │ │ -B97FC UID Size 04 (4) │ │ │ │ -B97FD UID 00000000 (0) │ │ │ │ -B9801 GID Size 04 (4) │ │ │ │ -B9802 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9806 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -B980A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B980B Created OS 03 (3) 'Unix' │ │ │ │ -B980C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B980D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B980E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9810 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9812 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9816 CRC AD016B67 (2902551399) │ │ │ │ -B981A Compressed Size 00000EC9 (3785) │ │ │ │ -B981E Uncompressed Size 000053BD (21437) │ │ │ │ -B9822 Filename Length 0021 (33) │ │ │ │ -B9824 Extra Length 0018 (24) │ │ │ │ -B9826 Comment Length 0000 (0) │ │ │ │ -B9828 Disk Start 0000 (0) │ │ │ │ -B982A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B982C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9830 Local Header Offset 000527FC (337916) │ │ │ │ -B9834 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9834: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9855 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9857 Length 0005 (5) │ │ │ │ -B9859 Flags 01 (1) 'Modification' │ │ │ │ -B985A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B985E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9860 Length 000B (11) │ │ │ │ -B9862 Version 01 (1) │ │ │ │ -B9863 UID Size 04 (4) │ │ │ │ -B9864 UID 00000000 (0) │ │ │ │ -B9868 GID Size 04 (4) │ │ │ │ -B9869 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B986D CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -B9871 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9872 Created OS 03 (3) 'Unix' │ │ │ │ -B9873 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9874 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9875 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9877 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9879 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B987D CRC 7E4C0584 (2118911364) │ │ │ │ -B9881 Compressed Size 00000534 (1332) │ │ │ │ -B9885 Uncompressed Size 00000C94 (3220) │ │ │ │ -B9889 Filename Length 0017 (23) │ │ │ │ -B988B Extra Length 0018 (24) │ │ │ │ -B988D Comment Length 0000 (0) │ │ │ │ -B988F Disk Start 0000 (0) │ │ │ │ -B9891 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9893 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9897 Local Header Offset 00053720 (341792) │ │ │ │ -B989B Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB989B: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B98B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B98B4 Length 0005 (5) │ │ │ │ -B98B6 Flags 01 (1) 'Modification' │ │ │ │ -B98B7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B98BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B98BD Length 000B (11) │ │ │ │ -B98BF Version 01 (1) │ │ │ │ -B98C0 UID Size 04 (4) │ │ │ │ -B98C1 UID 00000000 (0) │ │ │ │ -B98C5 GID Size 04 (4) │ │ │ │ -B98C6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B98CA CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -B98CE Created Zip Spec 3D (61) '6.1' │ │ │ │ -B98CF Created OS 03 (3) 'Unix' │ │ │ │ -B98D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B98D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B98D2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B98D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B98D6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B98DA CRC 76893EF6 (1988706038) │ │ │ │ -B98DE Compressed Size 00000465 (1125) │ │ │ │ -B98E2 Uncompressed Size 0000092F (2351) │ │ │ │ -B98E6 Filename Length 001B (27) │ │ │ │ -B98E8 Extra Length 0018 (24) │ │ │ │ -B98EA Comment Length 0000 (0) │ │ │ │ -B98EC Disk Start 0000 (0) │ │ │ │ -B98EE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B98F0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B98F4 Local Header Offset 00053CA5 (343205) │ │ │ │ -B98F8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB98F8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9913 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9915 Length 0005 (5) │ │ │ │ -B9917 Flags 01 (1) 'Modification' │ │ │ │ -B9918 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B991C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B991E Length 000B (11) │ │ │ │ -B9920 Version 01 (1) │ │ │ │ -B9921 UID Size 04 (4) │ │ │ │ -B9922 UID 00000000 (0) │ │ │ │ -B9926 GID Size 04 (4) │ │ │ │ -B9927 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B992B CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -B992F Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9930 Created OS 03 (3) 'Unix' │ │ │ │ -B9931 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9932 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9933 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9935 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9937 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B993B CRC A30F00DB (2735669467) │ │ │ │ -B993F Compressed Size 000016F7 (5879) │ │ │ │ -B9943 Uncompressed Size 00007A6B (31339) │ │ │ │ -B9947 Filename Length 001F (31) │ │ │ │ -B9949 Extra Length 0018 (24) │ │ │ │ -B994B Comment Length 0000 (0) │ │ │ │ -B994D Disk Start 0000 (0) │ │ │ │ -B994F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9951 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9955 Local Header Offset 0005415F (344415) │ │ │ │ -B9959 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9959: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9978 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B997A Length 0005 (5) │ │ │ │ -B997C Flags 01 (1) 'Modification' │ │ │ │ -B997D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9981 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9983 Length 000B (11) │ │ │ │ -B9985 Version 01 (1) │ │ │ │ -B9986 UID Size 04 (4) │ │ │ │ -B9987 UID 00000000 (0) │ │ │ │ -B998B GID Size 04 (4) │ │ │ │ -B998C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9990 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -B9994 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9995 Created OS 03 (3) 'Unix' │ │ │ │ -B9996 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9997 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9998 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B999A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B999C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B99A0 CRC 9EFDD8DE (2667436254) │ │ │ │ -B99A4 Compressed Size 00004161 (16737) │ │ │ │ -B99A8 Uncompressed Size 0001D15D (119133) │ │ │ │ -B99AC Filename Length 0010 (16) │ │ │ │ -B99AE Extra Length 0018 (24) │ │ │ │ -B99B0 Comment Length 0000 (0) │ │ │ │ -B99B2 Disk Start 0000 (0) │ │ │ │ -B99B4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B99B6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B99BA Local Header Offset 000558AF (350383) │ │ │ │ -B99BE Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB99BE: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B99CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B99D0 Length 0005 (5) │ │ │ │ -B99D2 Flags 01 (1) 'Modification' │ │ │ │ -B99D3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B99D7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B99D9 Length 000B (11) │ │ │ │ -B99DB Version 01 (1) │ │ │ │ -B99DC UID Size 04 (4) │ │ │ │ -B99DD UID 00000000 (0) │ │ │ │ -B99E1 GID Size 04 (4) │ │ │ │ -B99E2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B99E6 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -B99EA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B99EB Created OS 03 (3) 'Unix' │ │ │ │ -B99EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B99ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B99EE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B99F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B99F2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B99F6 CRC 5C6B0F1E (1550520094) │ │ │ │ -B99FA Compressed Size 00000A93 (2707) │ │ │ │ -B99FE Uncompressed Size 00002103 (8451) │ │ │ │ -B9A02 Filename Length 0014 (20) │ │ │ │ -B9A04 Extra Length 0018 (24) │ │ │ │ -B9A06 Comment Length 0000 (0) │ │ │ │ -B9A08 Disk Start 0000 (0) │ │ │ │ -B9A0A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9A0C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9A10 Local Header Offset 00059A5A (367194) │ │ │ │ -B9A14 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9A14: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9A28 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9A2A Length 0005 (5) │ │ │ │ -B9A2C Flags 01 (1) 'Modification' │ │ │ │ -B9A2D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9A31 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9A33 Length 000B (11) │ │ │ │ -B9A35 Version 01 (1) │ │ │ │ -B9A36 UID Size 04 (4) │ │ │ │ -B9A37 UID 00000000 (0) │ │ │ │ -B9A3B GID Size 04 (4) │ │ │ │ -B9A3C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9A40 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -B9A44 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9A45 Created OS 03 (3) 'Unix' │ │ │ │ -B9A46 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9A47 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9A48 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9A4A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9A4C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9A50 CRC C83C6772 (3359401842) │ │ │ │ -B9A54 Compressed Size 0000B53A (46394) │ │ │ │ -B9A58 Uncompressed Size 000418F9 (268537) │ │ │ │ -B9A5C Filename Length 0017 (23) │ │ │ │ -B9A5E Extra Length 0018 (24) │ │ │ │ -B9A60 Comment Length 0000 (0) │ │ │ │ -B9A62 Disk Start 0000 (0) │ │ │ │ -B9A64 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9A66 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9A6A Local Header Offset 0005A53B (369979) │ │ │ │ -B9A6E Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9A6E: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9A85 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9A87 Length 0005 (5) │ │ │ │ -B9A89 Flags 01 (1) 'Modification' │ │ │ │ -B9A8A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9A8E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9A90 Length 000B (11) │ │ │ │ -B9A92 Version 01 (1) │ │ │ │ -B9A93 UID Size 04 (4) │ │ │ │ -B9A94 UID 00000000 (0) │ │ │ │ -B9A98 GID Size 04 (4) │ │ │ │ -B9A99 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9A9D CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -B9AA1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9AA2 Created OS 03 (3) 'Unix' │ │ │ │ -B9AA3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9AA4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9AA5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9AA7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9AA9 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9AAD CRC 017E6141 (25059649) │ │ │ │ -B9AB1 Compressed Size 000003FE (1022) │ │ │ │ -B9AB5 Uncompressed Size 0000093B (2363) │ │ │ │ -B9AB9 Filename Length 0013 (19) │ │ │ │ -B9ABB Extra Length 0018 (24) │ │ │ │ -B9ABD Comment Length 0000 (0) │ │ │ │ -B9ABF Disk Start 0000 (0) │ │ │ │ -B9AC1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9AC3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9AC7 Local Header Offset 00065AC6 (416454) │ │ │ │ -B9ACB Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9ACB: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9ADE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9AE0 Length 0005 (5) │ │ │ │ -B9AE2 Flags 01 (1) 'Modification' │ │ │ │ -B9AE3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9AE7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9AE9 Length 000B (11) │ │ │ │ -B9AEB Version 01 (1) │ │ │ │ -B9AEC UID Size 04 (4) │ │ │ │ -B9AED UID 00000000 (0) │ │ │ │ -B9AF1 GID Size 04 (4) │ │ │ │ -B9AF2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9AF6 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -B9AFA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9AFB Created OS 03 (3) 'Unix' │ │ │ │ -B9AFC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9AFD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9AFE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9B00 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9B02 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9B06 CRC 0716D135 (118935861) │ │ │ │ -B9B0A Compressed Size 000014DB (5339) │ │ │ │ -B9B0E Uncompressed Size 00006890 (26768) │ │ │ │ -B9B12 Filename Length 0012 (18) │ │ │ │ -B9B14 Extra Length 0018 (24) │ │ │ │ -B9B16 Comment Length 0000 (0) │ │ │ │ -B9B18 Disk Start 0000 (0) │ │ │ │ -B9B1A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9B1C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9B20 Local Header Offset 00065F11 (417553) │ │ │ │ -B9B24 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9B24: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9B36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9B38 Length 0005 (5) │ │ │ │ -B9B3A Flags 01 (1) 'Modification' │ │ │ │ -B9B3B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9B3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9B41 Length 000B (11) │ │ │ │ -B9B43 Version 01 (1) │ │ │ │ -B9B44 UID Size 04 (4) │ │ │ │ -B9B45 UID 00000000 (0) │ │ │ │ -B9B49 GID Size 04 (4) │ │ │ │ -B9B4A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9B4E CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -B9B52 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9B53 Created OS 03 (3) 'Unix' │ │ │ │ -B9B54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9B55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9B56 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9B58 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9B5A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9B5E CRC 0C854523 (210060579) │ │ │ │ -B9B62 Compressed Size 000011EB (4587) │ │ │ │ -B9B66 Uncompressed Size 000040F8 (16632) │ │ │ │ -B9B6A Filename Length 0012 (18) │ │ │ │ -B9B6C Extra Length 0018 (24) │ │ │ │ -B9B6E Comment Length 0000 (0) │ │ │ │ -B9B70 Disk Start 0000 (0) │ │ │ │ -B9B72 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9B74 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9B78 Local Header Offset 00067438 (422968) │ │ │ │ -B9B7C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9B7C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9B8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9B90 Length 0005 (5) │ │ │ │ -B9B92 Flags 01 (1) 'Modification' │ │ │ │ -B9B93 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9B97 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9B99 Length 000B (11) │ │ │ │ -B9B9B Version 01 (1) │ │ │ │ -B9B9C UID Size 04 (4) │ │ │ │ -B9B9D UID 00000000 (0) │ │ │ │ -B9BA1 GID Size 04 (4) │ │ │ │ -B9BA2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9BA6 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -B9BAA Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9BAB Created OS 03 (3) 'Unix' │ │ │ │ -B9BAC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9BAD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9BAE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9BB0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9BB2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9BB6 CRC 3C82A845 (1015195717) │ │ │ │ -B9BBA Compressed Size 000009D8 (2520) │ │ │ │ -B9BBE Uncompressed Size 00003527 (13607) │ │ │ │ -B9BC2 Filename Length 0019 (25) │ │ │ │ -B9BC4 Extra Length 0018 (24) │ │ │ │ -B9BC6 Comment Length 0000 (0) │ │ │ │ -B9BC8 Disk Start 0000 (0) │ │ │ │ -B9BCA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9BCC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9BD0 Local Header Offset 0006866F (427631) │ │ │ │ -B9BD4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9BD4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9BED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9BEF Length 0005 (5) │ │ │ │ -B9BF1 Flags 01 (1) 'Modification' │ │ │ │ -B9BF2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9BF6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9BF8 Length 000B (11) │ │ │ │ -B9BFA Version 01 (1) │ │ │ │ -B9BFB UID Size 04 (4) │ │ │ │ -B9BFC UID 00000000 (0) │ │ │ │ -B9C00 GID Size 04 (4) │ │ │ │ -B9C01 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9C05 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -B9C09 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9C0A Created OS 03 (3) 'Unix' │ │ │ │ -B9C0B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9C0C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9C0D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9C0F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9C11 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9C15 CRC 7154FE10 (1901395472) │ │ │ │ -B9C19 Compressed Size 000018B3 (6323) │ │ │ │ -B9C1D Uncompressed Size 0000A676 (42614) │ │ │ │ -B9C21 Filename Length 0019 (25) │ │ │ │ -B9C23 Extra Length 0018 (24) │ │ │ │ -B9C25 Comment Length 0000 (0) │ │ │ │ -B9C27 Disk Start 0000 (0) │ │ │ │ -B9C29 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9C2B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9C2F Local Header Offset 0006909A (430234) │ │ │ │ -B9C33 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9C33: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9C4C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9C4E Length 0005 (5) │ │ │ │ -B9C50 Flags 01 (1) 'Modification' │ │ │ │ -B9C51 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9C55 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9C57 Length 000B (11) │ │ │ │ -B9C59 Version 01 (1) │ │ │ │ -B9C5A UID Size 04 (4) │ │ │ │ -B9C5B UID 00000000 (0) │ │ │ │ -B9C5F GID Size 04 (4) │ │ │ │ -B9C60 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9C64 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -B9C68 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9C69 Created OS 03 (3) 'Unix' │ │ │ │ -B9C6A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9C6B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9C6C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9C6E Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9C70 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9C74 CRC 28DDA60C (685614604) │ │ │ │ -B9C78 Compressed Size 0000177A (6010) │ │ │ │ -B9C7C Uncompressed Size 0000472A (18218) │ │ │ │ -B9C80 Filename Length 0014 (20) │ │ │ │ -B9C82 Extra Length 0018 (24) │ │ │ │ -B9C84 Comment Length 0000 (0) │ │ │ │ -B9C86 Disk Start 0000 (0) │ │ │ │ -B9C88 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9C8A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9C8E Local Header Offset 0006A9A0 (436640) │ │ │ │ -B9C92 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9C92: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9CA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9CA8 Length 0005 (5) │ │ │ │ -B9CAA Flags 01 (1) 'Modification' │ │ │ │ -B9CAB Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9CAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9CB1 Length 000B (11) │ │ │ │ -B9CB3 Version 01 (1) │ │ │ │ -B9CB4 UID Size 04 (4) │ │ │ │ -B9CB5 UID 00000000 (0) │ │ │ │ -B9CB9 GID Size 04 (4) │ │ │ │ -B9CBA GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9CBE CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -B9CC2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9CC3 Created OS 03 (3) 'Unix' │ │ │ │ -B9CC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9CC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9CC6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9CC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9CCA Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9CCE CRC AAF8364A (2868393546) │ │ │ │ -B9CD2 Compressed Size 00000408 (1032) │ │ │ │ -B9CD6 Uncompressed Size 00000823 (2083) │ │ │ │ -B9CDA Filename Length 001C (28) │ │ │ │ -B9CDC Extra Length 0018 (24) │ │ │ │ -B9CDE Comment Length 0000 (0) │ │ │ │ -B9CE0 Disk Start 0000 (0) │ │ │ │ -B9CE2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9CE4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9CE8 Local Header Offset 0006C168 (442728) │ │ │ │ -B9CEC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9CEC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9D08 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9D0A Length 0005 (5) │ │ │ │ -B9D0C Flags 01 (1) 'Modification' │ │ │ │ -B9D0D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9D11 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9D13 Length 000B (11) │ │ │ │ -B9D15 Version 01 (1) │ │ │ │ -B9D16 UID Size 04 (4) │ │ │ │ -B9D17 UID 00000000 (0) │ │ │ │ -B9D1B GID Size 04 (4) │ │ │ │ -B9D1C GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9D20 CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -B9D24 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9D25 Created OS 03 (3) 'Unix' │ │ │ │ -B9D26 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9D27 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9D28 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9D2A Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9D2C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9D30 CRC 50BEED75 (1354689909) │ │ │ │ -B9D34 Compressed Size 000024BE (9406) │ │ │ │ -B9D38 Uncompressed Size 0000B659 (46681) │ │ │ │ -B9D3C Filename Length 001F (31) │ │ │ │ -B9D3E Extra Length 0018 (24) │ │ │ │ -B9D40 Comment Length 0000 (0) │ │ │ │ -B9D42 Disk Start 0000 (0) │ │ │ │ -B9D44 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9D46 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9D4A Local Header Offset 0006C5C6 (443846) │ │ │ │ -B9D4E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9D4E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9D6D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9D6F Length 0005 (5) │ │ │ │ -B9D71 Flags 01 (1) 'Modification' │ │ │ │ -B9D72 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9D76 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9D78 Length 000B (11) │ │ │ │ -B9D7A Version 01 (1) │ │ │ │ -B9D7B UID Size 04 (4) │ │ │ │ -B9D7C UID 00000000 (0) │ │ │ │ -B9D80 GID Size 04 (4) │ │ │ │ -B9D81 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9D85 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -B9D89 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9D8A Created OS 03 (3) 'Unix' │ │ │ │ -B9D8B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9D8C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9D8D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9D8F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9D91 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9D95 CRC 6C44BBE5 (1816443877) │ │ │ │ -B9D99 Compressed Size 00000E7B (3707) │ │ │ │ -B9D9D Uncompressed Size 000052D7 (21207) │ │ │ │ -B9DA1 Filename Length 001F (31) │ │ │ │ -B9DA3 Extra Length 0018 (24) │ │ │ │ -B9DA5 Comment Length 0000 (0) │ │ │ │ -B9DA7 Disk Start 0000 (0) │ │ │ │ -B9DA9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9DAB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9DAF Local Header Offset 0006EADD (453341) │ │ │ │ -B9DB3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9DB3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9DD2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9DD4 Length 0005 (5) │ │ │ │ -B9DD6 Flags 01 (1) 'Modification' │ │ │ │ -B9DD7 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9DDB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9DDD Length 000B (11) │ │ │ │ -B9DDF Version 01 (1) │ │ │ │ -B9DE0 UID Size 04 (4) │ │ │ │ -B9DE1 UID 00000000 (0) │ │ │ │ -B9DE5 GID Size 04 (4) │ │ │ │ -B9DE6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9DEA CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -B9DEE Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9DEF Created OS 03 (3) 'Unix' │ │ │ │ -B9DF0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9DF1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9DF2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9DF4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9DF6 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9DFA CRC 337E1414 (863900692) │ │ │ │ -B9DFE Compressed Size 00000A42 (2626) │ │ │ │ -B9E02 Uncompressed Size 00002478 (9336) │ │ │ │ -B9E06 Filename Length 0013 (19) │ │ │ │ -B9E08 Extra Length 0018 (24) │ │ │ │ -B9E0A Comment Length 0000 (0) │ │ │ │ -B9E0C Disk Start 0000 (0) │ │ │ │ -B9E0E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9E10 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9E14 Local Header Offset 0006F9B1 (457137) │ │ │ │ -B9E18 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9E18: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9E2B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9E2D Length 0005 (5) │ │ │ │ -B9E2F Flags 01 (1) 'Modification' │ │ │ │ -B9E30 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9E34 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9E36 Length 000B (11) │ │ │ │ -B9E38 Version 01 (1) │ │ │ │ -B9E39 UID Size 04 (4) │ │ │ │ -B9E3A UID 00000000 (0) │ │ │ │ -B9E3E GID Size 04 (4) │ │ │ │ -B9E3F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9E43 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -B9E47 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9E48 Created OS 03 (3) 'Unix' │ │ │ │ -B9E49 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9E4A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9E4B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9E4D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9E4F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9E53 CRC F602667E (4127352446) │ │ │ │ -B9E57 Compressed Size 00002549 (9545) │ │ │ │ -B9E5B Uncompressed Size 0000B9C7 (47559) │ │ │ │ -B9E5F Filename Length 0019 (25) │ │ │ │ -B9E61 Extra Length 0018 (24) │ │ │ │ -B9E63 Comment Length 0000 (0) │ │ │ │ -B9E65 Disk Start 0000 (0) │ │ │ │ -B9E67 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9E69 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9E6D Local Header Offset 00070440 (459840) │ │ │ │ -B9E71 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9E71: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9E8A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9E8C Length 0005 (5) │ │ │ │ -B9E8E Flags 01 (1) 'Modification' │ │ │ │ -B9E8F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9E93 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9E95 Length 000B (11) │ │ │ │ -B9E97 Version 01 (1) │ │ │ │ -B9E98 UID Size 04 (4) │ │ │ │ -B9E99 UID 00000000 (0) │ │ │ │ -B9E9D GID Size 04 (4) │ │ │ │ -B9E9E GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9EA2 CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -B9EA6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9EA7 Created OS 03 (3) 'Unix' │ │ │ │ -B9EA8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9EA9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9EAA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9EAC Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9EAE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9EB2 CRC 0EF470FF (250900735) │ │ │ │ -B9EB6 Compressed Size 00000EF8 (3832) │ │ │ │ -B9EBA Uncompressed Size 00003A2A (14890) │ │ │ │ -B9EBE Filename Length 0024 (36) │ │ │ │ -B9EC0 Extra Length 0018 (24) │ │ │ │ -B9EC2 Comment Length 0000 (0) │ │ │ │ -B9EC4 Disk Start 0000 (0) │ │ │ │ -B9EC6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9EC8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9ECC Local Header Offset 000729DC (469468) │ │ │ │ -B9ED0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9ED0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9EF4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9EF6 Length 0005 (5) │ │ │ │ -B9EF8 Flags 01 (1) 'Modification' │ │ │ │ -B9EF9 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9EFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9EFF Length 000B (11) │ │ │ │ -B9F01 Version 01 (1) │ │ │ │ -B9F02 UID Size 04 (4) │ │ │ │ -B9F03 UID 00000000 (0) │ │ │ │ -B9F07 GID Size 04 (4) │ │ │ │ -B9F08 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9F0C CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -B9F10 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9F11 Created OS 03 (3) 'Unix' │ │ │ │ -B9F12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9F13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9F14 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9F16 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9F18 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9F1C CRC AA7C1B5F (2860260191) │ │ │ │ -B9F20 Compressed Size 00001AB3 (6835) │ │ │ │ -B9F24 Uncompressed Size 00005F00 (24320) │ │ │ │ -B9F28 Filename Length 0017 (23) │ │ │ │ -B9F2A Extra Length 0018 (24) │ │ │ │ -B9F2C Comment Length 0000 (0) │ │ │ │ -B9F2E Disk Start 0000 (0) │ │ │ │ -B9F30 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9F32 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9F36 Local Header Offset 00073932 (473394) │ │ │ │ -B9F3A Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9F3A: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9F51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9F53 Length 0005 (5) │ │ │ │ -B9F55 Flags 01 (1) 'Modification' │ │ │ │ -B9F56 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9F5A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9F5C Length 000B (11) │ │ │ │ -B9F5E Version 01 (1) │ │ │ │ -B9F5F UID Size 04 (4) │ │ │ │ -B9F60 UID 00000000 (0) │ │ │ │ -B9F64 GID Size 04 (4) │ │ │ │ -B9F65 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9F69 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -B9F6D Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9F6E Created OS 03 (3) 'Unix' │ │ │ │ -B9F6F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9F70 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9F71 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9F73 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9F75 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9F79 CRC 11E32AF1 (300100337) │ │ │ │ -B9F7D Compressed Size 00000ED3 (3795) │ │ │ │ -B9F81 Uncompressed Size 000038E2 (14562) │ │ │ │ -B9F85 Filename Length 0023 (35) │ │ │ │ -B9F87 Extra Length 0018 (24) │ │ │ │ -B9F89 Comment Length 0000 (0) │ │ │ │ -B9F8B Disk Start 0000 (0) │ │ │ │ -B9F8D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9F8F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9F93 Local Header Offset 00075436 (480310) │ │ │ │ -B9F97 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9F97: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9FBA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9FBC Length 0005 (5) │ │ │ │ -B9FBE Flags 01 (1) 'Modification' │ │ │ │ -B9FBF Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -B9FC3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9FC5 Length 000B (11) │ │ │ │ -B9FC7 Version 01 (1) │ │ │ │ -B9FC8 UID Size 04 (4) │ │ │ │ -B9FC9 UID 00000000 (0) │ │ │ │ -B9FCD GID Size 04 (4) │ │ │ │ -B9FCE GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9FD2 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -B9FD6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9FD7 Created OS 03 (3) 'Unix' │ │ │ │ -B9FD8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9FD9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9FDA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9FDC Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9FDE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -B9FE2 CRC 2DB7929F (767005343) │ │ │ │ -B9FE6 Compressed Size 00000113 (275) │ │ │ │ -B9FEA Uncompressed Size 000001F3 (499) │ │ │ │ -B9FEE Filename Length 001B (27) │ │ │ │ -B9FF0 Extra Length 0018 (24) │ │ │ │ -B9FF2 Comment Length 0000 (0) │ │ │ │ -B9FF4 Disk Start 0000 (0) │ │ │ │ -B9FF6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9FF8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9FFC Local Header Offset 00076366 (484198) │ │ │ │ -BA000 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA000: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA01B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA01D Length 0005 (5) │ │ │ │ -BA01F Flags 01 (1) 'Modification' │ │ │ │ -BA020 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA024 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA026 Length 000B (11) │ │ │ │ -BA028 Version 01 (1) │ │ │ │ -BA029 UID Size 04 (4) │ │ │ │ -BA02A UID 00000000 (0) │ │ │ │ -BA02E GID Size 04 (4) │ │ │ │ -BA02F GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA033 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -BA037 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA038 Created OS 03 (3) 'Unix' │ │ │ │ -BA039 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA03A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA03B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA03D Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA03F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA043 CRC 1466816A (342262122) │ │ │ │ -BA047 Compressed Size 00001890 (6288) │ │ │ │ -BA04B Uncompressed Size 00008FAA (36778) │ │ │ │ -BA04F Filename Length 001D (29) │ │ │ │ -BA051 Extra Length 0018 (24) │ │ │ │ -BA053 Comment Length 0000 (0) │ │ │ │ -BA055 Disk Start 0000 (0) │ │ │ │ -BA057 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA059 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA05D Local Header Offset 000764CE (484558) │ │ │ │ -BA061 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA061: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA07E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA080 Length 0005 (5) │ │ │ │ -BA082 Flags 01 (1) 'Modification' │ │ │ │ -BA083 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA087 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA089 Length 000B (11) │ │ │ │ -BA08B Version 01 (1) │ │ │ │ -BA08C UID Size 04 (4) │ │ │ │ -BA08D UID 00000000 (0) │ │ │ │ -BA091 GID Size 04 (4) │ │ │ │ -BA092 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA096 CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -BA09A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA09B Created OS 03 (3) 'Unix' │ │ │ │ -BA09C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA09D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA09E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA0A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA0A2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA0A6 CRC 227D9DE1 (578657761) │ │ │ │ -BA0AA Compressed Size 0000164B (5707) │ │ │ │ -BA0AE Uncompressed Size 00003A99 (15001) │ │ │ │ -BA0B2 Filename Length 0015 (21) │ │ │ │ -BA0B4 Extra Length 0018 (24) │ │ │ │ -BA0B6 Comment Length 0000 (0) │ │ │ │ -BA0B8 Disk Start 0000 (0) │ │ │ │ -BA0BA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA0BC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA0C0 Local Header Offset 00077DB5 (490933) │ │ │ │ -BA0C4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA0C4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA0D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA0DB Length 0005 (5) │ │ │ │ -BA0DD Flags 01 (1) 'Modification' │ │ │ │ -BA0DE Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA0E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA0E4 Length 000B (11) │ │ │ │ -BA0E6 Version 01 (1) │ │ │ │ -BA0E7 UID Size 04 (4) │ │ │ │ -BA0E8 UID 00000000 (0) │ │ │ │ -BA0EC GID Size 04 (4) │ │ │ │ -BA0ED GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA0F1 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -BA0F5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA0F6 Created OS 03 (3) 'Unix' │ │ │ │ -BA0F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA0F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA0F9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA0FB Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA0FD Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA101 CRC CEAA5B5B (3467271003) │ │ │ │ -BA105 Compressed Size 00003D38 (15672) │ │ │ │ -BA109 Uncompressed Size 0001219B (74139) │ │ │ │ -BA10D Filename Length 0016 (22) │ │ │ │ -BA10F Extra Length 0018 (24) │ │ │ │ -BA111 Comment Length 0000 (0) │ │ │ │ -BA113 Disk Start 0000 (0) │ │ │ │ -BA115 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA117 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA11B Local Header Offset 0007944F (496719) │ │ │ │ -BA11F Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA11F: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA135 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA137 Length 0005 (5) │ │ │ │ -BA139 Flags 01 (1) 'Modification' │ │ │ │ -BA13A Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA13E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA140 Length 000B (11) │ │ │ │ -BA142 Version 01 (1) │ │ │ │ -BA143 UID Size 04 (4) │ │ │ │ -BA144 UID 00000000 (0) │ │ │ │ -BA148 GID Size 04 (4) │ │ │ │ -BA149 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA14D CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -BA151 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA152 Created OS 03 (3) 'Unix' │ │ │ │ -BA153 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA154 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA155 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA157 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA159 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA15D CRC E619BA48 (3860445768) │ │ │ │ -BA161 Compressed Size 00003E85 (16005) │ │ │ │ -BA165 Uncompressed Size 0001C179 (115065) │ │ │ │ -BA169 Filename Length 0019 (25) │ │ │ │ -BA16B Extra Length 0018 (24) │ │ │ │ -BA16D Comment Length 0000 (0) │ │ │ │ -BA16F Disk Start 0000 (0) │ │ │ │ -BA171 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA173 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA177 Local Header Offset 0007D1D7 (512471) │ │ │ │ -BA17B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA17B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA194 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA196 Length 0005 (5) │ │ │ │ -BA198 Flags 01 (1) 'Modification' │ │ │ │ -BA199 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA19D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA19F Length 000B (11) │ │ │ │ -BA1A1 Version 01 (1) │ │ │ │ -BA1A2 UID Size 04 (4) │ │ │ │ -BA1A3 UID 00000000 (0) │ │ │ │ -BA1A7 GID Size 04 (4) │ │ │ │ -BA1A8 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA1AC CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -BA1B0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA1B1 Created OS 03 (3) 'Unix' │ │ │ │ -BA1B2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA1B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA1B4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA1B6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA1B8 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA1BC CRC E446B5DF (3829839327) │ │ │ │ -BA1C0 Compressed Size 0000088A (2186) │ │ │ │ -BA1C4 Uncompressed Size 0000362C (13868) │ │ │ │ -BA1C8 Filename Length 0011 (17) │ │ │ │ -BA1CA Extra Length 0018 (24) │ │ │ │ -BA1CC Comment Length 0000 (0) │ │ │ │ -BA1CE Disk Start 0000 (0) │ │ │ │ -BA1D0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA1D2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA1D6 Local Header Offset 000810AF (528559) │ │ │ │ -BA1DA Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA1DA: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA1EB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA1ED Length 0005 (5) │ │ │ │ -BA1EF Flags 01 (1) 'Modification' │ │ │ │ -BA1F0 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA1F4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA1F6 Length 000B (11) │ │ │ │ -BA1F8 Version 01 (1) │ │ │ │ -BA1F9 UID Size 04 (4) │ │ │ │ -BA1FA UID 00000000 (0) │ │ │ │ -BA1FE GID Size 04 (4) │ │ │ │ -BA1FF GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA203 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -BA207 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA208 Created OS 03 (3) 'Unix' │ │ │ │ -BA209 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA20A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA20B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA20D Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA20F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA213 CRC 3249F529 (843707689) │ │ │ │ -BA217 Compressed Size 000051B7 (20919) │ │ │ │ -BA21B Uncompressed Size 0001FBDD (130013) │ │ │ │ -BA21F Filename Length 0015 (21) │ │ │ │ -BA221 Extra Length 0018 (24) │ │ │ │ -BA223 Comment Length 0000 (0) │ │ │ │ -BA225 Disk Start 0000 (0) │ │ │ │ -BA227 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA229 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA22D Local Header Offset 00081984 (530820) │ │ │ │ -BA231 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA231: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA246 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA248 Length 0005 (5) │ │ │ │ -BA24A Flags 01 (1) 'Modification' │ │ │ │ -BA24B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA24F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA251 Length 000B (11) │ │ │ │ -BA253 Version 01 (1) │ │ │ │ -BA254 UID Size 04 (4) │ │ │ │ -BA255 UID 00000000 (0) │ │ │ │ -BA259 GID Size 04 (4) │ │ │ │ -BA25A GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA25E CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -BA262 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA263 Created OS 03 (3) 'Unix' │ │ │ │ -BA264 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA265 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA266 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA268 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA26A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA26E CRC 91C1F665 (2445407845) │ │ │ │ -BA272 Compressed Size 00001AFA (6906) │ │ │ │ -BA276 Uncompressed Size 00008257 (33367) │ │ │ │ -BA27A Filename Length 0019 (25) │ │ │ │ -BA27C Extra Length 0018 (24) │ │ │ │ -BA27E Comment Length 0000 (0) │ │ │ │ -BA280 Disk Start 0000 (0) │ │ │ │ -BA282 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA284 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA288 Local Header Offset 00086B8A (551818) │ │ │ │ -BA28C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA28C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA2A5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA2A7 Length 0005 (5) │ │ │ │ -BA2A9 Flags 01 (1) 'Modification' │ │ │ │ -BA2AA Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA2AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA2B0 Length 000B (11) │ │ │ │ -BA2B2 Version 01 (1) │ │ │ │ -BA2B3 UID Size 04 (4) │ │ │ │ -BA2B4 UID 00000000 (0) │ │ │ │ -BA2B8 GID Size 04 (4) │ │ │ │ -BA2B9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA2BD CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -BA2C1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA2C2 Created OS 03 (3) 'Unix' │ │ │ │ -BA2C3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA2C4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA2C5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA2C7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA2C9 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA2CD CRC A5421022 (2772570146) │ │ │ │ -BA2D1 Compressed Size 00000D95 (3477) │ │ │ │ -BA2D5 Uncompressed Size 00002E9D (11933) │ │ │ │ -BA2D9 Filename Length 0018 (24) │ │ │ │ -BA2DB Extra Length 0018 (24) │ │ │ │ -BA2DD Comment Length 0000 (0) │ │ │ │ -BA2DF Disk Start 0000 (0) │ │ │ │ -BA2E1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA2E3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA2E7 Local Header Offset 000886D7 (558807) │ │ │ │ -BA2EB Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA2EB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA303 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA305 Length 0005 (5) │ │ │ │ -BA307 Flags 01 (1) 'Modification' │ │ │ │ -BA308 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA30C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA30E Length 000B (11) │ │ │ │ -BA310 Version 01 (1) │ │ │ │ -BA311 UID Size 04 (4) │ │ │ │ -BA312 UID 00000000 (0) │ │ │ │ -BA316 GID Size 04 (4) │ │ │ │ -BA317 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA31B CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -BA31F Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA320 Created OS 03 (3) 'Unix' │ │ │ │ -BA321 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA322 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA323 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA325 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA327 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA32B CRC 42771396 (1115100054) │ │ │ │ -BA32F Compressed Size 000001DF (479) │ │ │ │ -BA333 Uncompressed Size 00000321 (801) │ │ │ │ -BA337 Filename Length 0011 (17) │ │ │ │ -BA339 Extra Length 0018 (24) │ │ │ │ -BA33B Comment Length 0000 (0) │ │ │ │ -BA33D Disk Start 0000 (0) │ │ │ │ -BA33F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA341 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA345 Local Header Offset 000894BE (562366) │ │ │ │ -BA349 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA349: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA35A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA35C Length 0005 (5) │ │ │ │ -BA35E Flags 01 (1) 'Modification' │ │ │ │ -BA35F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA363 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA365 Length 000B (11) │ │ │ │ -BA367 Version 01 (1) │ │ │ │ -BA368 UID Size 04 (4) │ │ │ │ -BA369 UID 00000000 (0) │ │ │ │ -BA36D GID Size 04 (4) │ │ │ │ -BA36E GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA372 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -BA376 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA377 Created OS 03 (3) 'Unix' │ │ │ │ -BA378 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA379 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA37A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA37C Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA37E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA382 CRC DB0CDFF8 (3675054072) │ │ │ │ -BA386 Compressed Size 000006BC (1724) │ │ │ │ -BA38A Uncompressed Size 0000141C (5148) │ │ │ │ -BA38E Filename Length 0019 (25) │ │ │ │ -BA390 Extra Length 0018 (24) │ │ │ │ -BA392 Comment Length 0000 (0) │ │ │ │ -BA394 Disk Start 0000 (0) │ │ │ │ -BA396 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA398 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA39C Local Header Offset 000896E8 (562920) │ │ │ │ -BA3A0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA3A0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA3B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA3BB Length 0005 (5) │ │ │ │ -BA3BD Flags 01 (1) 'Modification' │ │ │ │ -BA3BE Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA3C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA3C4 Length 000B (11) │ │ │ │ -BA3C6 Version 01 (1) │ │ │ │ -BA3C7 UID Size 04 (4) │ │ │ │ -BA3C8 UID 00000000 (0) │ │ │ │ -BA3CC GID Size 04 (4) │ │ │ │ -BA3CD GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA3D1 CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -BA3D5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA3D6 Created OS 03 (3) 'Unix' │ │ │ │ -BA3D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA3D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA3D9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA3DB Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA3DD Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA3E1 CRC 6202EE3B (1644359227) │ │ │ │ -BA3E5 Compressed Size 00001B8A (7050) │ │ │ │ -BA3E9 Uncompressed Size 00009F5D (40797) │ │ │ │ -BA3ED Filename Length 0018 (24) │ │ │ │ -BA3EF Extra Length 0018 (24) │ │ │ │ -BA3F1 Comment Length 0000 (0) │ │ │ │ -BA3F3 Disk Start 0000 (0) │ │ │ │ -BA3F5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA3F7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA3FB Local Header Offset 00089DF7 (564727) │ │ │ │ -BA3FF Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA3FF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA417 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA419 Length 0005 (5) │ │ │ │ -BA41B Flags 01 (1) 'Modification' │ │ │ │ -BA41C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA420 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA422 Length 000B (11) │ │ │ │ -BA424 Version 01 (1) │ │ │ │ -BA425 UID Size 04 (4) │ │ │ │ -BA426 UID 00000000 (0) │ │ │ │ -BA42A GID Size 04 (4) │ │ │ │ -BA42B GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA42F CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -BA433 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA434 Created OS 03 (3) 'Unix' │ │ │ │ -BA435 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA436 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA437 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA439 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA43B Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA43F CRC 6A8C22D8 (1787568856) │ │ │ │ -BA443 Compressed Size 000016FB (5883) │ │ │ │ -BA447 Uncompressed Size 00008B10 (35600) │ │ │ │ -BA44B Filename Length 0012 (18) │ │ │ │ -BA44D Extra Length 0018 (24) │ │ │ │ -BA44F Comment Length 0000 (0) │ │ │ │ -BA451 Disk Start 0000 (0) │ │ │ │ -BA453 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA455 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA459 Local Header Offset 0008B9D3 (571859) │ │ │ │ -BA45D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA45D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA46F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA471 Length 0005 (5) │ │ │ │ -BA473 Flags 01 (1) 'Modification' │ │ │ │ -BA474 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA478 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA47A Length 000B (11) │ │ │ │ -BA47C Version 01 (1) │ │ │ │ -BA47D UID Size 04 (4) │ │ │ │ -BA47E UID 00000000 (0) │ │ │ │ -BA482 GID Size 04 (4) │ │ │ │ -BA483 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA487 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -BA48B Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA48C Created OS 03 (3) 'Unix' │ │ │ │ -BA48D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA48E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA48F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA491 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA493 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA497 CRC 627017A3 (1651513251) │ │ │ │ -BA49B Compressed Size 00001E0A (7690) │ │ │ │ -BA49F Uncompressed Size 00008801 (34817) │ │ │ │ -BA4A3 Filename Length 0016 (22) │ │ │ │ -BA4A5 Extra Length 0018 (24) │ │ │ │ -BA4A7 Comment Length 0000 (0) │ │ │ │ -BA4A9 Disk Start 0000 (0) │ │ │ │ -BA4AB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA4AD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA4B1 Local Header Offset 0008D11A (577818) │ │ │ │ -BA4B5 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA4B5: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA4CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA4CD Length 0005 (5) │ │ │ │ -BA4CF Flags 01 (1) 'Modification' │ │ │ │ -BA4D0 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA4D4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA4D6 Length 000B (11) │ │ │ │ -BA4D8 Version 01 (1) │ │ │ │ -BA4D9 UID Size 04 (4) │ │ │ │ -BA4DA UID 00000000 (0) │ │ │ │ -BA4DE GID Size 04 (4) │ │ │ │ -BA4DF GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA4E3 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -BA4E7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA4E8 Created OS 03 (3) 'Unix' │ │ │ │ -BA4E9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA4EA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA4EB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA4ED Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA4EF Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA4F3 CRC 47B38AFD (1202948861) │ │ │ │ -BA4F7 Compressed Size 000029A8 (10664) │ │ │ │ -BA4FB Uncompressed Size 0000D04D (53325) │ │ │ │ -BA4FF Filename Length 001A (26) │ │ │ │ -BA501 Extra Length 0018 (24) │ │ │ │ -BA503 Comment Length 0000 (0) │ │ │ │ -BA505 Disk Start 0000 (0) │ │ │ │ -BA507 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA509 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA50D Local Header Offset 0008EF74 (585588) │ │ │ │ -BA511 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA511: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA52B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA52D Length 0005 (5) │ │ │ │ -BA52F Flags 01 (1) 'Modification' │ │ │ │ -BA530 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA534 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA536 Length 000B (11) │ │ │ │ -BA538 Version 01 (1) │ │ │ │ -BA539 UID Size 04 (4) │ │ │ │ -BA53A UID 00000000 (0) │ │ │ │ -BA53E GID Size 04 (4) │ │ │ │ -BA53F GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA543 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -BA547 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA548 Created OS 03 (3) 'Unix' │ │ │ │ -BA549 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA54A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA54B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA54D Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA54F Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA553 CRC 0141A5BD (21079485) │ │ │ │ -BA557 Compressed Size 000009A9 (2473) │ │ │ │ -BA55B Uncompressed Size 00001DB4 (7604) │ │ │ │ -BA55F Filename Length 0018 (24) │ │ │ │ -BA561 Extra Length 0018 (24) │ │ │ │ -BA563 Comment Length 0000 (0) │ │ │ │ -BA565 Disk Start 0000 (0) │ │ │ │ -BA567 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA569 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA56D Local Header Offset 00091970 (596336) │ │ │ │ -BA571 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA571: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA589 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA58B Length 0005 (5) │ │ │ │ -BA58D Flags 01 (1) 'Modification' │ │ │ │ -BA58E Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA592 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA594 Length 000B (11) │ │ │ │ -BA596 Version 01 (1) │ │ │ │ -BA597 UID Size 04 (4) │ │ │ │ -BA598 UID 00000000 (0) │ │ │ │ -BA59C GID Size 04 (4) │ │ │ │ -BA59D GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA5A1 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -BA5A5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA5A6 Created OS 03 (3) 'Unix' │ │ │ │ -BA5A7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA5A8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA5A9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA5AB Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA5AD Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA5B1 CRC F0556E9A (4032130714) │ │ │ │ -BA5B5 Compressed Size 000152EE (86766) │ │ │ │ -BA5B9 Uncompressed Size 000159F8 (88568) │ │ │ │ -BA5BD Filename Length 001E (30) │ │ │ │ -BA5BF Extra Length 0018 (24) │ │ │ │ -BA5C1 Comment Length 0000 (0) │ │ │ │ -BA5C3 Disk Start 0000 (0) │ │ │ │ -BA5C5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA5C7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA5CB Local Header Offset 0009236B (598891) │ │ │ │ -BA5CF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA5CF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA5ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA5EF Length 0005 (5) │ │ │ │ -BA5F1 Flags 01 (1) 'Modification' │ │ │ │ -BA5F2 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA5F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA5F8 Length 000B (11) │ │ │ │ -BA5FA Version 01 (1) │ │ │ │ -BA5FB UID Size 04 (4) │ │ │ │ -BA5FC UID 00000000 (0) │ │ │ │ -BA600 GID Size 04 (4) │ │ │ │ -BA601 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA605 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -BA609 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA60A Created OS 03 (3) 'Unix' │ │ │ │ -BA60B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA60C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA60D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA60F Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA611 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA615 CRC F5E2129F (4125233823) │ │ │ │ -BA619 Compressed Size 000016BC (5820) │ │ │ │ -BA61D Uncompressed Size 000016CD (5837) │ │ │ │ -BA621 Filename Length 0015 (21) │ │ │ │ -BA623 Extra Length 0018 (24) │ │ │ │ -BA625 Comment Length 0000 (0) │ │ │ │ -BA627 Disk Start 0000 (0) │ │ │ │ -BA629 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA62B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA62F Local Header Offset 000A76B1 (685745) │ │ │ │ -BA633 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA633: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA648 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA64A Length 0005 (5) │ │ │ │ -BA64C Flags 01 (1) 'Modification' │ │ │ │ -BA64D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA651 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA653 Length 000B (11) │ │ │ │ -BA655 Version 01 (1) │ │ │ │ -BA656 UID Size 04 (4) │ │ │ │ -BA657 UID 00000000 (0) │ │ │ │ -BA65B GID Size 04 (4) │ │ │ │ -BA65C GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA660 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -BA664 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA665 Created OS 03 (3) 'Unix' │ │ │ │ -BA666 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA667 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA668 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA66A Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA66C Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA670 CRC F5E2129F (4125233823) │ │ │ │ -BA674 Compressed Size 000016BC (5820) │ │ │ │ -BA678 Uncompressed Size 000016CD (5837) │ │ │ │ -BA67C Filename Length 001C (28) │ │ │ │ -BA67E Extra Length 0018 (24) │ │ │ │ -BA680 Comment Length 0000 (0) │ │ │ │ -BA682 Disk Start 0000 (0) │ │ │ │ -BA684 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA686 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA68A Local Header Offset 000A8DBC (691644) │ │ │ │ -BA68E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA68E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA6AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA6AC Length 0005 (5) │ │ │ │ -BA6AE Flags 01 (1) 'Modification' │ │ │ │ -BA6AF Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA6B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA6B5 Length 000B (11) │ │ │ │ -BA6B7 Version 01 (1) │ │ │ │ -BA6B8 UID Size 04 (4) │ │ │ │ -BA6B9 UID 00000000 (0) │ │ │ │ -BA6BD GID Size 04 (4) │ │ │ │ -BA6BE GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA6C2 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -BA6C6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA6C7 Created OS 03 (3) 'Unix' │ │ │ │ -BA6C8 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA6C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA6CA General Purpose Flag 0000 (0) │ │ │ │ -BA6CC Compression Method 0000 (0) 'Stored' │ │ │ │ -BA6CE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA6D2 CRC FC95F24B (4237685323) │ │ │ │ -BA6D6 Compressed Size 00001B84 (7044) │ │ │ │ -BA6DA Uncompressed Size 00001B84 (7044) │ │ │ │ -BA6DE Filename Length 0016 (22) │ │ │ │ -BA6E0 Extra Length 0018 (24) │ │ │ │ -BA6E2 Comment Length 0000 (0) │ │ │ │ -BA6E4 Disk Start 0000 (0) │ │ │ │ -BA6E6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA6E8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA6EC Local Header Offset 000AA4CE (697550) │ │ │ │ -BA6F0 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA6F0: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA706 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA708 Length 0005 (5) │ │ │ │ -BA70A Flags 01 (1) 'Modification' │ │ │ │ -BA70B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA70F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA711 Length 000B (11) │ │ │ │ -BA713 Version 01 (1) │ │ │ │ -BA714 UID Size 04 (4) │ │ │ │ -BA715 UID 00000000 (0) │ │ │ │ -BA719 GID Size 04 (4) │ │ │ │ -BA71A GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA71E CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -BA722 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA723 Created OS 03 (3) 'Unix' │ │ │ │ -BA724 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA725 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA726 General Purpose Flag 0000 (0) │ │ │ │ -BA728 Compression Method 0000 (0) 'Stored' │ │ │ │ -BA72A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA72E CRC D0D71F86 (3503759238) │ │ │ │ -BA732 Compressed Size 00000B7B (2939) │ │ │ │ -BA736 Uncompressed Size 00000B7B (2939) │ │ │ │ -BA73A Filename Length 0016 (22) │ │ │ │ -BA73C Extra Length 0018 (24) │ │ │ │ -BA73E Comment Length 0000 (0) │ │ │ │ -BA740 Disk Start 0000 (0) │ │ │ │ -BA742 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA744 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA748 Local Header Offset 000AC0A2 (704674) │ │ │ │ -BA74C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA74C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA762 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA764 Length 0005 (5) │ │ │ │ -BA766 Flags 01 (1) 'Modification' │ │ │ │ -BA767 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA76B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA76D Length 000B (11) │ │ │ │ -BA76F Version 01 (1) │ │ │ │ -BA770 UID Size 04 (4) │ │ │ │ -BA771 UID 00000000 (0) │ │ │ │ -BA775 GID Size 04 (4) │ │ │ │ -BA776 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA77A CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -BA77E Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA77F Created OS 03 (3) 'Unix' │ │ │ │ -BA780 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA781 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA782 General Purpose Flag 0000 (0) │ │ │ │ -BA784 Compression Method 0000 (0) 'Stored' │ │ │ │ -BA786 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA78A CRC FFF9C4D2 (4294558930) │ │ │ │ -BA78E Compressed Size 0000138F (5007) │ │ │ │ -BA792 Uncompressed Size 0000138F (5007) │ │ │ │ -BA796 Filename Length 0016 (22) │ │ │ │ -BA798 Extra Length 0018 (24) │ │ │ │ -BA79A Comment Length 0000 (0) │ │ │ │ -BA79C Disk Start 0000 (0) │ │ │ │ -BA79E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA7A0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA7A4 Local Header Offset 000ACC6D (707693) │ │ │ │ -BA7A8 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA7A8: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA7BE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA7C0 Length 0005 (5) │ │ │ │ -BA7C2 Flags 01 (1) 'Modification' │ │ │ │ -BA7C3 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA7C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA7C9 Length 000B (11) │ │ │ │ -BA7CB Version 01 (1) │ │ │ │ -BA7CC UID Size 04 (4) │ │ │ │ -BA7CD UID 00000000 (0) │ │ │ │ -BA7D1 GID Size 04 (4) │ │ │ │ -BA7D2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA7D6 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -BA7DA Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA7DB Created OS 03 (3) 'Unix' │ │ │ │ -BA7DC Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA7DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA7DE General Purpose Flag 0000 (0) │ │ │ │ -BA7E0 Compression Method 0000 (0) 'Stored' │ │ │ │ -BA7E2 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA7E6 CRC A1037E8E (2701360782) │ │ │ │ -BA7EA Compressed Size 0000145E (5214) │ │ │ │ -BA7EE Uncompressed Size 0000145E (5214) │ │ │ │ -BA7F2 Filename Length 0016 (22) │ │ │ │ -BA7F4 Extra Length 0018 (24) │ │ │ │ -BA7F6 Comment Length 0000 (0) │ │ │ │ -BA7F8 Disk Start 0000 (0) │ │ │ │ -BA7FA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA7FC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA800 Local Header Offset 000AE04C (712780) │ │ │ │ -BA804 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA804: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA81A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA81C Length 0005 (5) │ │ │ │ -BA81E Flags 01 (1) 'Modification' │ │ │ │ -BA81F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA823 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA825 Length 000B (11) │ │ │ │ -BA827 Version 01 (1) │ │ │ │ -BA828 UID Size 04 (4) │ │ │ │ -BA829 UID 00000000 (0) │ │ │ │ -BA82D GID Size 04 (4) │ │ │ │ -BA82E GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA832 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -BA836 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA837 Created OS 03 (3) 'Unix' │ │ │ │ -BA838 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA839 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA83A General Purpose Flag 0000 (0) │ │ │ │ -BA83C Compression Method 0000 (0) 'Stored' │ │ │ │ -BA83E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA842 CRC 5E9E64F1 (1587438833) │ │ │ │ -BA846 Compressed Size 000008EC (2284) │ │ │ │ -BA84A Uncompressed Size 000008EC (2284) │ │ │ │ -BA84E Filename Length 0016 (22) │ │ │ │ -BA850 Extra Length 0018 (24) │ │ │ │ -BA852 Comment Length 0000 (0) │ │ │ │ -BA854 Disk Start 0000 (0) │ │ │ │ -BA856 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA858 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA85C Local Header Offset 000AF4FA (718074) │ │ │ │ -BA860 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA860: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA876 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA878 Length 0005 (5) │ │ │ │ -BA87A Flags 01 (1) 'Modification' │ │ │ │ -BA87B Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA87F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA881 Length 000B (11) │ │ │ │ -BA883 Version 01 (1) │ │ │ │ -BA884 UID Size 04 (4) │ │ │ │ -BA885 UID 00000000 (0) │ │ │ │ -BA889 GID Size 04 (4) │ │ │ │ -BA88A GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA88E CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ -BA892 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA893 Created OS 03 (3) 'Unix' │ │ │ │ -BA894 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BA895 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA896 General Purpose Flag 0000 (0) │ │ │ │ -BA898 Compression Method 0000 (0) 'Stored' │ │ │ │ -BA89A Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA89E CRC 42E340AB (1122189483) │ │ │ │ -BA8A2 Compressed Size 00001F2E (7982) │ │ │ │ -BA8A6 Uncompressed Size 00001F2E (7982) │ │ │ │ -BA8AA Filename Length 001E (30) │ │ │ │ -BA8AC Extra Length 0018 (24) │ │ │ │ -BA8AE Comment Length 0000 (0) │ │ │ │ -BA8B0 Disk Start 0000 (0) │ │ │ │ -BA8B2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA8B4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA8B8 Local Header Offset 000AFE36 (720438) │ │ │ │ -BA8BC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA8BC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA8DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA8DC Length 0005 (5) │ │ │ │ -BA8DE Flags 01 (1) 'Modification' │ │ │ │ -BA8DF Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA8E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA8E5 Length 000B (11) │ │ │ │ -BA8E7 Version 01 (1) │ │ │ │ -BA8E8 UID Size 04 (4) │ │ │ │ -BA8E9 UID 00000000 (0) │ │ │ │ -BA8ED GID Size 04 (4) │ │ │ │ -BA8EE GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA8F2 CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ -BA8F6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA8F7 Created OS 03 (3) 'Unix' │ │ │ │ -BA8F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA8F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA8FA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA8FC Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA8FE Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA902 CRC 93BCFC9A (2478636186) │ │ │ │ -BA906 Compressed Size 00003D67 (15719) │ │ │ │ -BA90A Uncompressed Size 0001664D (91725) │ │ │ │ -BA90E Filename Length 001A (26) │ │ │ │ -BA910 Extra Length 0018 (24) │ │ │ │ -BA912 Comment Length 0000 (0) │ │ │ │ -BA914 Disk Start 0000 (0) │ │ │ │ -BA916 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA918 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA91C Local Header Offset 000B1DBC (728508) │ │ │ │ -BA920 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA920: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA93A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA93C Length 0005 (5) │ │ │ │ -BA93E Flags 01 (1) 'Modification' │ │ │ │ -BA93F Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA943 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA945 Length 000B (11) │ │ │ │ -BA947 Version 01 (1) │ │ │ │ -BA948 UID Size 04 (4) │ │ │ │ -BA949 UID 00000000 (0) │ │ │ │ -BA94D GID Size 04 (4) │ │ │ │ -BA94E GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA952 CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ -BA956 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA957 Created OS 03 (3) 'Unix' │ │ │ │ -BA958 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA959 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA95A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA95C Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA95E Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA962 CRC 3A3194F5 (976327925) │ │ │ │ -BA966 Compressed Size 000029D3 (10707) │ │ │ │ -BA96A Uncompressed Size 0000BB37 (47927) │ │ │ │ -BA96E Filename Length 0018 (24) │ │ │ │ -BA970 Extra Length 0018 (24) │ │ │ │ -BA972 Comment Length 0000 (0) │ │ │ │ -BA974 Disk Start 0000 (0) │ │ │ │ -BA976 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA978 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA97C Local Header Offset 000B5B77 (744311) │ │ │ │ -BA980 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA980: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA998 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA99A Length 0005 (5) │ │ │ │ -BA99C Flags 01 (1) 'Modification' │ │ │ │ -BA99D Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA9A1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA9A3 Length 000B (11) │ │ │ │ -BA9A5 Version 01 (1) │ │ │ │ -BA9A6 UID Size 04 (4) │ │ │ │ -BA9A7 UID 00000000 (0) │ │ │ │ -BA9AB GID Size 04 (4) │ │ │ │ -BA9AC GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA9B0 CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ -BA9B4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA9B5 Created OS 03 (3) 'Unix' │ │ │ │ -BA9B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA9B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA9B8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA9BA Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA9BC Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BA9C0 CRC DCB3B516 (3702764822) │ │ │ │ -BA9C4 Compressed Size 000000AE (174) │ │ │ │ -BA9C8 Uncompressed Size 000000FC (252) │ │ │ │ -BA9CC Filename Length 0016 (22) │ │ │ │ -BA9CE Extra Length 0018 (24) │ │ │ │ -BA9D0 Comment Length 0000 (0) │ │ │ │ -BA9D2 Disk Start 0000 (0) │ │ │ │ -BA9D4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA9D6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA9DA Local Header Offset 000B859C (755100) │ │ │ │ -BA9DE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA9DE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA9F4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA9F6 Length 0005 (5) │ │ │ │ -BA9F8 Flags 01 (1) 'Modification' │ │ │ │ -BA9F9 Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BA9FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA9FF Length 000B (11) │ │ │ │ -BAA01 Version 01 (1) │ │ │ │ -BAA02 UID Size 04 (4) │ │ │ │ -BAA03 UID 00000000 (0) │ │ │ │ -BAA07 GID Size 04 (4) │ │ │ │ -BAA08 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAA0C CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ -BAA10 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAA11 Created OS 03 (3) 'Unix' │ │ │ │ -BAA12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAA13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAA14 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAA16 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAA18 Modification Time 5C2F6EB3 (1546612403) 'Thu Jan 15 13:53:38 2026' │ │ │ │ -BAA1C CRC 58439733 (1480824627) │ │ │ │ -BAA20 Compressed Size 00000077 (119) │ │ │ │ -BAA24 Uncompressed Size 000000A2 (162) │ │ │ │ -BAA28 Filename Length 002D (45) │ │ │ │ -BAA2A Extra Length 0018 (24) │ │ │ │ -BAA2C Comment Length 0000 (0) │ │ │ │ -BAA2E Disk Start 0000 (0) │ │ │ │ -BAA30 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAA32 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAA36 Local Header Offset 000B869A (755354) │ │ │ │ -BAA3A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAA3A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAA67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAA69 Length 0005 (5) │ │ │ │ -BAA6B Flags 01 (1) 'Modification' │ │ │ │ -BAA6C Modification Time 6968F163 (1768485219) 'Thu Jan 15 13:53:39 2026' │ │ │ │ -BAA70 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAA72 Length 000B (11) │ │ │ │ -BAA74 Version 01 (1) │ │ │ │ -BAA75 UID Size 04 (4) │ │ │ │ -BAA76 UID 00000000 (0) │ │ │ │ -BAA7A GID Size 04 (4) │ │ │ │ -BAA7B GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAA7F END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -BAA83 Number of this disk 0000 (0) │ │ │ │ -BAA85 Central Dir Disk no 0000 (0) │ │ │ │ -BAA87 Entries in this disk 0060 (96) │ │ │ │ -BAA89 Total Entries 0060 (96) │ │ │ │ -BAA8B Size of Central Dir 00002307 (8967) │ │ │ │ -BAA8F Offset to Central Dir 000B8778 (755576) │ │ │ │ -BAA93 Comment Length 0000 (0) │ │ │ │ +B8678 LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ +B867C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B867D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B867E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8680 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8682 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8686 CRC 58439733 (1480824627) │ │ │ │ +B868A Compressed Size 00000077 (119) │ │ │ │ +B868E Uncompressed Size 000000A2 (162) │ │ │ │ +B8692 Filename Length 002D (45) │ │ │ │ +B8694 Extra Length 001C (28) │ │ │ │ +B8696 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8696: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B86C3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B86C5 Length 0009 (9) │ │ │ │ +B86C7 Flags 03 (3) 'Modification Access' │ │ │ │ +B86C8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B86CC Access Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B86D0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B86D2 Length 000B (11) │ │ │ │ +B86D4 Version 01 (1) │ │ │ │ +B86D5 UID Size 04 (4) │ │ │ │ +B86D6 UID 00000000 (0) │ │ │ │ +B86DA GID Size 04 (4) │ │ │ │ +B86DB GID 00000000 (0) │ │ │ │ +B86DF PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +B8756 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +B875A Created Zip Spec 3D (61) '6.1' │ │ │ │ +B875B Created OS 03 (3) 'Unix' │ │ │ │ +B875C Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B875D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B875E General Purpose Flag 0000 (0) │ │ │ │ +B8760 Compression Method 0000 (0) 'Stored' │ │ │ │ +B8762 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8766 CRC 2CAB616F (749429103) │ │ │ │ +B876A Compressed Size 00000014 (20) │ │ │ │ +B876E Uncompressed Size 00000014 (20) │ │ │ │ +B8772 Filename Length 0008 (8) │ │ │ │ +B8774 Extra Length 0018 (24) │ │ │ │ +B8776 Comment Length 0000 (0) │ │ │ │ +B8778 Disk Start 0000 (0) │ │ │ │ +B877A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B877C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8780 Local Header Offset 00000000 (0) │ │ │ │ +B8784 Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8784: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B878C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B878E Length 0005 (5) │ │ │ │ +B8790 Flags 01 (1) 'Modification' │ │ │ │ +B8791 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8795 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8797 Length 000B (11) │ │ │ │ +B8799 Version 01 (1) │ │ │ │ +B879A UID Size 04 (4) │ │ │ │ +B879B UID 00000000 (0) │ │ │ │ +B879F GID Size 04 (4) │ │ │ │ +B87A0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B87A4 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +B87A8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B87A9 Created OS 03 (3) 'Unix' │ │ │ │ +B87AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B87AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B87AC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B87AE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B87B0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B87B4 CRC 41D9871E (1104774942) │ │ │ │ +B87B8 Compressed Size 00000D29 (3369) │ │ │ │ +B87BC Uncompressed Size 00003931 (14641) │ │ │ │ +B87C0 Filename Length 001B (27) │ │ │ │ +B87C2 Extra Length 0018 (24) │ │ │ │ +B87C4 Comment Length 0000 (0) │ │ │ │ +B87C6 Disk Start 0000 (0) │ │ │ │ +B87C8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B87CA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B87CE Local Header Offset 00000056 (86) │ │ │ │ +B87D2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB87D2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B87ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B87EF Length 0005 (5) │ │ │ │ +B87F1 Flags 01 (1) 'Modification' │ │ │ │ +B87F2 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B87F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B87F8 Length 000B (11) │ │ │ │ +B87FA Version 01 (1) │ │ │ │ +B87FB UID Size 04 (4) │ │ │ │ +B87FC UID 00000000 (0) │ │ │ │ +B8800 GID Size 04 (4) │ │ │ │ +B8801 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8805 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +B8809 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B880A Created OS 03 (3) 'Unix' │ │ │ │ +B880B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B880C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B880D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B880F Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8811 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8815 CRC DE70EF53 (3731943251) │ │ │ │ +B8819 Compressed Size 000015AC (5548) │ │ │ │ +B881D Uncompressed Size 00004600 (17920) │ │ │ │ +B8821 Filename Length 0014 (20) │ │ │ │ +B8823 Extra Length 0018 (24) │ │ │ │ +B8825 Comment Length 0000 (0) │ │ │ │ +B8827 Disk Start 0000 (0) │ │ │ │ +B8829 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B882B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B882F Local Header Offset 00000DD4 (3540) │ │ │ │ +B8833 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8833: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8847 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8849 Length 0005 (5) │ │ │ │ +B884B Flags 01 (1) 'Modification' │ │ │ │ +B884C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8850 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8852 Length 000B (11) │ │ │ │ +B8854 Version 01 (1) │ │ │ │ +B8855 UID Size 04 (4) │ │ │ │ +B8856 UID 00000000 (0) │ │ │ │ +B885A GID Size 04 (4) │ │ │ │ +B885B GID 00000000 (0) │ │ │ │ + │ │ │ │ +B885F CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +B8863 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8864 Created OS 03 (3) 'Unix' │ │ │ │ +B8865 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8866 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8867 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8869 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B886B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B886F CRC 4F1D5C6A (1327324266) │ │ │ │ +B8873 Compressed Size 000006D3 (1747) │ │ │ │ +B8877 Uncompressed Size 0000123F (4671) │ │ │ │ +B887B Filename Length 0013 (19) │ │ │ │ +B887D Extra Length 0018 (24) │ │ │ │ +B887F Comment Length 0000 (0) │ │ │ │ +B8881 Disk Start 0000 (0) │ │ │ │ +B8883 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8885 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8889 Local Header Offset 000023CE (9166) │ │ │ │ +B888D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB888D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B88A0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B88A2 Length 0005 (5) │ │ │ │ +B88A4 Flags 01 (1) 'Modification' │ │ │ │ +B88A5 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B88A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B88AB Length 000B (11) │ │ │ │ +B88AD Version 01 (1) │ │ │ │ +B88AE UID Size 04 (4) │ │ │ │ +B88AF UID 00000000 (0) │ │ │ │ +B88B3 GID Size 04 (4) │ │ │ │ +B88B4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B88B8 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +B88BC Created Zip Spec 3D (61) '6.1' │ │ │ │ +B88BD Created OS 03 (3) 'Unix' │ │ │ │ +B88BE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B88BF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B88C0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B88C2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B88C4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B88C8 CRC 6D3889B0 (1832421808) │ │ │ │ +B88CC Compressed Size 00002E6C (11884) │ │ │ │ +B88D0 Uncompressed Size 0000D4B1 (54449) │ │ │ │ +B88D4 Filename Length 0014 (20) │ │ │ │ +B88D6 Extra Length 0018 (24) │ │ │ │ +B88D8 Comment Length 0000 (0) │ │ │ │ +B88DA Disk Start 0000 (0) │ │ │ │ +B88DC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B88DE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B88E2 Local Header Offset 00002AEE (10990) │ │ │ │ +B88E6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB88E6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B88FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B88FC Length 0005 (5) │ │ │ │ +B88FE Flags 01 (1) 'Modification' │ │ │ │ +B88FF Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8903 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8905 Length 000B (11) │ │ │ │ +B8907 Version 01 (1) │ │ │ │ +B8908 UID Size 04 (4) │ │ │ │ +B8909 UID 00000000 (0) │ │ │ │ +B890D GID Size 04 (4) │ │ │ │ +B890E GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8912 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +B8916 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8917 Created OS 03 (3) 'Unix' │ │ │ │ +B8918 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8919 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B891A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B891C Compression Method 0008 (8) 'Deflated' │ │ │ │ +B891E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8922 CRC 5467FF9A (1416101786) │ │ │ │ +B8926 Compressed Size 000003ED (1005) │ │ │ │ +B892A Uncompressed Size 00000874 (2164) │ │ │ │ +B892E Filename Length 0014 (20) │ │ │ │ +B8930 Extra Length 0018 (24) │ │ │ │ +B8932 Comment Length 0000 (0) │ │ │ │ +B8934 Disk Start 0000 (0) │ │ │ │ +B8936 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8938 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B893C Local Header Offset 000059A8 (22952) │ │ │ │ +B8940 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8940: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8954 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8956 Length 0005 (5) │ │ │ │ +B8958 Flags 01 (1) 'Modification' │ │ │ │ +B8959 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B895D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B895F Length 000B (11) │ │ │ │ +B8961 Version 01 (1) │ │ │ │ +B8962 UID Size 04 (4) │ │ │ │ +B8963 UID 00000000 (0) │ │ │ │ +B8967 GID Size 04 (4) │ │ │ │ +B8968 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B896C CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +B8970 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8971 Created OS 03 (3) 'Unix' │ │ │ │ +B8972 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8973 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8974 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8976 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8978 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B897C CRC 58D4C95E (1490340190) │ │ │ │ +B8980 Compressed Size 000001AC (428) │ │ │ │ +B8984 Uncompressed Size 000002F8 (760) │ │ │ │ +B8988 Filename Length 0011 (17) │ │ │ │ +B898A Extra Length 0018 (24) │ │ │ │ +B898C Comment Length 0000 (0) │ │ │ │ +B898E Disk Start 0000 (0) │ │ │ │ +B8990 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8992 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8996 Local Header Offset 00005DE3 (24035) │ │ │ │ +B899A Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB899A: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B89AB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B89AD Length 0005 (5) │ │ │ │ +B89AF Flags 01 (1) 'Modification' │ │ │ │ +B89B0 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B89B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B89B6 Length 000B (11) │ │ │ │ +B89B8 Version 01 (1) │ │ │ │ +B89B9 UID Size 04 (4) │ │ │ │ +B89BA UID 00000000 (0) │ │ │ │ +B89BE GID Size 04 (4) │ │ │ │ +B89BF GID 00000000 (0) │ │ │ │ + │ │ │ │ +B89C3 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +B89C7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B89C8 Created OS 03 (3) 'Unix' │ │ │ │ +B89C9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B89CA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B89CB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B89CD Compression Method 0008 (8) 'Deflated' │ │ │ │ +B89CF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B89D3 CRC D80B7767 (3624630119) │ │ │ │ +B89D7 Compressed Size 000020C8 (8392) │ │ │ │ +B89DB Uncompressed Size 0000B4AE (46254) │ │ │ │ +B89DF Filename Length 001B (27) │ │ │ │ +B89E1 Extra Length 0018 (24) │ │ │ │ +B89E3 Comment Length 0000 (0) │ │ │ │ +B89E5 Disk Start 0000 (0) │ │ │ │ +B89E7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B89E9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B89ED Local Header Offset 00005FDA (24538) │ │ │ │ +B89F1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB89F1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8A0C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8A0E Length 0005 (5) │ │ │ │ +B8A10 Flags 01 (1) 'Modification' │ │ │ │ +B8A11 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8A15 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8A17 Length 000B (11) │ │ │ │ +B8A19 Version 01 (1) │ │ │ │ +B8A1A UID Size 04 (4) │ │ │ │ +B8A1B UID 00000000 (0) │ │ │ │ +B8A1F GID Size 04 (4) │ │ │ │ +B8A20 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8A24 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +B8A28 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8A29 Created OS 03 (3) 'Unix' │ │ │ │ +B8A2A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8A2B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8A2C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8A2E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8A30 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8A34 CRC 5EB57D9F (1588952479) │ │ │ │ +B8A38 Compressed Size 00000E64 (3684) │ │ │ │ +B8A3C Uncompressed Size 00003090 (12432) │ │ │ │ +B8A40 Filename Length 001D (29) │ │ │ │ +B8A42 Extra Length 0018 (24) │ │ │ │ +B8A44 Comment Length 0000 (0) │ │ │ │ +B8A46 Disk Start 0000 (0) │ │ │ │ +B8A48 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8A4A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8A4E Local Header Offset 000080F7 (33015) │ │ │ │ +B8A52 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8A52: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8A6F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8A71 Length 0005 (5) │ │ │ │ +B8A73 Flags 01 (1) 'Modification' │ │ │ │ +B8A74 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8A78 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8A7A Length 000B (11) │ │ │ │ +B8A7C Version 01 (1) │ │ │ │ +B8A7D UID Size 04 (4) │ │ │ │ +B8A7E UID 00000000 (0) │ │ │ │ +B8A82 GID Size 04 (4) │ │ │ │ +B8A83 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8A87 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +B8A8B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8A8C Created OS 03 (3) 'Unix' │ │ │ │ +B8A8D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8A8E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8A8F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8A91 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8A93 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8A97 CRC 15AABA29 (363510313) │ │ │ │ +B8A9B Compressed Size 00000991 (2449) │ │ │ │ +B8A9F Uncompressed Size 00001D3B (7483) │ │ │ │ +B8AA3 Filename Length 0019 (25) │ │ │ │ +B8AA5 Extra Length 0018 (24) │ │ │ │ +B8AA7 Comment Length 0000 (0) │ │ │ │ +B8AA9 Disk Start 0000 (0) │ │ │ │ +B8AAB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8AAD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8AB1 Local Header Offset 00008FB2 (36786) │ │ │ │ +B8AB5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8AB5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8ACE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8AD0 Length 0005 (5) │ │ │ │ +B8AD2 Flags 01 (1) 'Modification' │ │ │ │ +B8AD3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8AD7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8AD9 Length 000B (11) │ │ │ │ +B8ADB Version 01 (1) │ │ │ │ +B8ADC UID Size 04 (4) │ │ │ │ +B8ADD UID 00000000 (0) │ │ │ │ +B8AE1 GID Size 04 (4) │ │ │ │ +B8AE2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8AE6 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +B8AEA Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8AEB Created OS 03 (3) 'Unix' │ │ │ │ +B8AEC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8AED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8AEE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8AF0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8AF2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8AF6 CRC 9F9CDC44 (2677857348) │ │ │ │ +B8AFA Compressed Size 00003878 (14456) │ │ │ │ +B8AFE Uncompressed Size 0000F7F2 (63474) │ │ │ │ +B8B02 Filename Length 0015 (21) │ │ │ │ +B8B04 Extra Length 0018 (24) │ │ │ │ +B8B06 Comment Length 0000 (0) │ │ │ │ +B8B08 Disk Start 0000 (0) │ │ │ │ +B8B0A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8B0C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8B10 Local Header Offset 00009996 (39318) │ │ │ │ +B8B14 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8B14: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8B29 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8B2B Length 0005 (5) │ │ │ │ +B8B2D Flags 01 (1) 'Modification' │ │ │ │ +B8B2E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8B32 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8B34 Length 000B (11) │ │ │ │ +B8B36 Version 01 (1) │ │ │ │ +B8B37 UID Size 04 (4) │ │ │ │ +B8B38 UID 00000000 (0) │ │ │ │ +B8B3C GID Size 04 (4) │ │ │ │ +B8B3D GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8B41 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +B8B45 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8B46 Created OS 03 (3) 'Unix' │ │ │ │ +B8B47 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8B48 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8B49 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8B4B Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8B4D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8B51 CRC C3F9818D (3287908749) │ │ │ │ +B8B55 Compressed Size 0000AB05 (43781) │ │ │ │ +B8B59 Uncompressed Size 0003E04F (254031) │ │ │ │ +B8B5D Filename Length 0012 (18) │ │ │ │ +B8B5F Extra Length 0018 (24) │ │ │ │ +B8B61 Comment Length 0000 (0) │ │ │ │ +B8B63 Disk Start 0000 (0) │ │ │ │ +B8B65 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8B67 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8B6B Local Header Offset 0000D25D (53853) │ │ │ │ +B8B6F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8B6F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8B81 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8B83 Length 0005 (5) │ │ │ │ +B8B85 Flags 01 (1) 'Modification' │ │ │ │ +B8B86 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8B8A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8B8C Length 000B (11) │ │ │ │ +B8B8E Version 01 (1) │ │ │ │ +B8B8F UID Size 04 (4) │ │ │ │ +B8B90 UID 00000000 (0) │ │ │ │ +B8B94 GID Size 04 (4) │ │ │ │ +B8B95 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8B99 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +B8B9D Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8B9E Created OS 03 (3) 'Unix' │ │ │ │ +B8B9F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8BA0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8BA1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8BA3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8BA5 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8BA9 CRC 36241BA5 (908336037) │ │ │ │ +B8BAD Compressed Size 00003B0A (15114) │ │ │ │ +B8BB1 Uncompressed Size 0001B46A (111722) │ │ │ │ +B8BB5 Filename Length 0015 (21) │ │ │ │ +B8BB7 Extra Length 0018 (24) │ │ │ │ +B8BB9 Comment Length 0000 (0) │ │ │ │ +B8BBB Disk Start 0000 (0) │ │ │ │ +B8BBD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8BBF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8BC3 Local Header Offset 00017DAE (97710) │ │ │ │ +B8BC7 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8BC7: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8BDC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8BDE Length 0005 (5) │ │ │ │ +B8BE0 Flags 01 (1) 'Modification' │ │ │ │ +B8BE1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8BE5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8BE7 Length 000B (11) │ │ │ │ +B8BE9 Version 01 (1) │ │ │ │ +B8BEA UID Size 04 (4) │ │ │ │ +B8BEB UID 00000000 (0) │ │ │ │ +B8BEF GID Size 04 (4) │ │ │ │ +B8BF0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8BF4 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +B8BF8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8BF9 Created OS 03 (3) 'Unix' │ │ │ │ +B8BFA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8BFB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8BFC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8BFE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8C00 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8C04 CRC B962F612 (3110270482) │ │ │ │ +B8C08 Compressed Size 00009199 (37273) │ │ │ │ +B8C0C Uncompressed Size 0003D5E7 (251367) │ │ │ │ +B8C10 Filename Length 0014 (20) │ │ │ │ +B8C12 Extra Length 0018 (24) │ │ │ │ +B8C14 Comment Length 0000 (0) │ │ │ │ +B8C16 Disk Start 0000 (0) │ │ │ │ +B8C18 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8C1A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8C1E Local Header Offset 0001B907 (112903) │ │ │ │ +B8C22 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8C22: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8C36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8C38 Length 0005 (5) │ │ │ │ +B8C3A Flags 01 (1) 'Modification' │ │ │ │ +B8C3B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8C3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8C41 Length 000B (11) │ │ │ │ +B8C43 Version 01 (1) │ │ │ │ +B8C44 UID Size 04 (4) │ │ │ │ +B8C45 UID 00000000 (0) │ │ │ │ +B8C49 GID Size 04 (4) │ │ │ │ +B8C4A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8C4E CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +B8C52 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8C53 Created OS 03 (3) 'Unix' │ │ │ │ +B8C54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8C55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8C56 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8C58 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8C5A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8C5E CRC 3A7A1761 (981079905) │ │ │ │ +B8C62 Compressed Size 00001217 (4631) │ │ │ │ +B8C66 Uncompressed Size 00003C8F (15503) │ │ │ │ +B8C6A Filename Length 0010 (16) │ │ │ │ +B8C6C Extra Length 0018 (24) │ │ │ │ +B8C6E Comment Length 0000 (0) │ │ │ │ +B8C70 Disk Start 0000 (0) │ │ │ │ +B8C72 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8C74 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8C78 Local Header Offset 00024AEE (150254) │ │ │ │ +B8C7C Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8C7C: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8C8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8C8E Length 0005 (5) │ │ │ │ +B8C90 Flags 01 (1) 'Modification' │ │ │ │ +B8C91 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8C95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8C97 Length 000B (11) │ │ │ │ +B8C99 Version 01 (1) │ │ │ │ +B8C9A UID Size 04 (4) │ │ │ │ +B8C9B UID 00000000 (0) │ │ │ │ +B8C9F GID Size 04 (4) │ │ │ │ +B8CA0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8CA4 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +B8CA8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8CA9 Created OS 03 (3) 'Unix' │ │ │ │ +B8CAA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8CAB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8CAC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8CAE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8CB0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8CB4 CRC F17726AF (4051117743) │ │ │ │ +B8CB8 Compressed Size 00002A72 (10866) │ │ │ │ +B8CBC Uncompressed Size 0001151D (70941) │ │ │ │ +B8CC0 Filename Length 0016 (22) │ │ │ │ +B8CC2 Extra Length 0018 (24) │ │ │ │ +B8CC4 Comment Length 0000 (0) │ │ │ │ +B8CC6 Disk Start 0000 (0) │ │ │ │ +B8CC8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8CCA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8CCE Local Header Offset 00025D4F (154959) │ │ │ │ +B8CD2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8CD2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8CE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8CEA Length 0005 (5) │ │ │ │ +B8CEC Flags 01 (1) 'Modification' │ │ │ │ +B8CED Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8CF1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8CF3 Length 000B (11) │ │ │ │ +B8CF5 Version 01 (1) │ │ │ │ +B8CF6 UID Size 04 (4) │ │ │ │ +B8CF7 UID 00000000 (0) │ │ │ │ +B8CFB GID Size 04 (4) │ │ │ │ +B8CFC GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8D00 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +B8D04 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8D05 Created OS 03 (3) 'Unix' │ │ │ │ +B8D06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8D07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8D08 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8D0A Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8D0C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8D10 CRC 89A3D790 (2309216144) │ │ │ │ +B8D14 Compressed Size 000014D8 (5336) │ │ │ │ +B8D18 Uncompressed Size 0000518B (20875) │ │ │ │ +B8D1C Filename Length 001D (29) │ │ │ │ +B8D1E Extra Length 0018 (24) │ │ │ │ +B8D20 Comment Length 0000 (0) │ │ │ │ +B8D22 Disk Start 0000 (0) │ │ │ │ +B8D24 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8D26 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8D2A Local Header Offset 00028811 (165905) │ │ │ │ +B8D2E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8D2E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8D4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8D4D Length 0005 (5) │ │ │ │ +B8D4F Flags 01 (1) 'Modification' │ │ │ │ +B8D50 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8D54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8D56 Length 000B (11) │ │ │ │ +B8D58 Version 01 (1) │ │ │ │ +B8D59 UID Size 04 (4) │ │ │ │ +B8D5A UID 00000000 (0) │ │ │ │ +B8D5E GID Size 04 (4) │ │ │ │ +B8D5F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8D63 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +B8D67 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8D68 Created OS 03 (3) 'Unix' │ │ │ │ +B8D69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8D6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8D6B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8D6D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8D6F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8D73 CRC 9734CAB9 (2536819385) │ │ │ │ +B8D77 Compressed Size 00003803 (14339) │ │ │ │ +B8D7B Uncompressed Size 0000EA4A (59978) │ │ │ │ +B8D7F Filename Length 001C (28) │ │ │ │ +B8D81 Extra Length 0018 (24) │ │ │ │ +B8D83 Comment Length 0000 (0) │ │ │ │ +B8D85 Disk Start 0000 (0) │ │ │ │ +B8D87 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8D89 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8D8D Local Header Offset 00029D40 (171328) │ │ │ │ +B8D91 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8D91: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8DAD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8DAF Length 0005 (5) │ │ │ │ +B8DB1 Flags 01 (1) 'Modification' │ │ │ │ +B8DB2 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8DB6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8DB8 Length 000B (11) │ │ │ │ +B8DBA Version 01 (1) │ │ │ │ +B8DBB UID Size 04 (4) │ │ │ │ +B8DBC UID 00000000 (0) │ │ │ │ +B8DC0 GID Size 04 (4) │ │ │ │ +B8DC1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8DC5 CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +B8DC9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8DCA Created OS 03 (3) 'Unix' │ │ │ │ +B8DCB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8DCC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8DCD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8DCF Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8DD1 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8DD5 CRC 9B224725 (2602714917) │ │ │ │ +B8DD9 Compressed Size 0000069E (1694) │ │ │ │ +B8DDD Uncompressed Size 000011F2 (4594) │ │ │ │ +B8DE1 Filename Length 001C (28) │ │ │ │ +B8DE3 Extra Length 0018 (24) │ │ │ │ +B8DE5 Comment Length 0000 (0) │ │ │ │ +B8DE7 Disk Start 0000 (0) │ │ │ │ +B8DE9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8DEB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8DEF Local Header Offset 0002D599 (185753) │ │ │ │ +B8DF3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8DF3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8E0F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8E11 Length 0005 (5) │ │ │ │ +B8E13 Flags 01 (1) 'Modification' │ │ │ │ +B8E14 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8E18 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8E1A Length 000B (11) │ │ │ │ +B8E1C Version 01 (1) │ │ │ │ +B8E1D UID Size 04 (4) │ │ │ │ +B8E1E UID 00000000 (0) │ │ │ │ +B8E22 GID Size 04 (4) │ │ │ │ +B8E23 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8E27 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +B8E2B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8E2C Created OS 03 (3) 'Unix' │ │ │ │ +B8E2D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8E2E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8E2F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8E31 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8E33 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8E37 CRC 6671B663 (1718728291) │ │ │ │ +B8E3B Compressed Size 0000107A (4218) │ │ │ │ +B8E3F Uncompressed Size 00004BFD (19453) │ │ │ │ +B8E43 Filename Length 001B (27) │ │ │ │ +B8E45 Extra Length 0018 (24) │ │ │ │ +B8E47 Comment Length 0000 (0) │ │ │ │ +B8E49 Disk Start 0000 (0) │ │ │ │ +B8E4B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8E4D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8E51 Local Header Offset 0002DC8D (187533) │ │ │ │ +B8E55 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8E55: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8E70 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8E72 Length 0005 (5) │ │ │ │ +B8E74 Flags 01 (1) 'Modification' │ │ │ │ +B8E75 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8E79 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8E7B Length 000B (11) │ │ │ │ +B8E7D Version 01 (1) │ │ │ │ +B8E7E UID Size 04 (4) │ │ │ │ +B8E7F UID 00000000 (0) │ │ │ │ +B8E83 GID Size 04 (4) │ │ │ │ +B8E84 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8E88 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +B8E8C Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8E8D Created OS 03 (3) 'Unix' │ │ │ │ +B8E8E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8E8F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8E90 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8E92 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8E94 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8E98 CRC 9C84C038 (2625945656) │ │ │ │ +B8E9C Compressed Size 00003BE0 (15328) │ │ │ │ +B8EA0 Uncompressed Size 0000D781 (55169) │ │ │ │ +B8EA4 Filename Length 001D (29) │ │ │ │ +B8EA6 Extra Length 0018 (24) │ │ │ │ +B8EA8 Comment Length 0000 (0) │ │ │ │ +B8EAA Disk Start 0000 (0) │ │ │ │ +B8EAC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8EAE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8EB2 Local Header Offset 0002ED5C (191836) │ │ │ │ +B8EB6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8EB6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8ED3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8ED5 Length 0005 (5) │ │ │ │ +B8ED7 Flags 01 (1) 'Modification' │ │ │ │ +B8ED8 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8EDC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8EDE Length 000B (11) │ │ │ │ +B8EE0 Version 01 (1) │ │ │ │ +B8EE1 UID Size 04 (4) │ │ │ │ +B8EE2 UID 00000000 (0) │ │ │ │ +B8EE6 GID Size 04 (4) │ │ │ │ +B8EE7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8EEB CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +B8EEF Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8EF0 Created OS 03 (3) 'Unix' │ │ │ │ +B8EF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8EF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8EF3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8EF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8EF7 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8EFB CRC 7CC10163 (2093023587) │ │ │ │ +B8EFF Compressed Size 00000D66 (3430) │ │ │ │ +B8F03 Uncompressed Size 0000388B (14475) │ │ │ │ +B8F07 Filename Length 001D (29) │ │ │ │ +B8F09 Extra Length 0018 (24) │ │ │ │ +B8F0B Comment Length 0000 (0) │ │ │ │ +B8F0D Disk Start 0000 (0) │ │ │ │ +B8F0F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8F11 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8F15 Local Header Offset 00032993 (207251) │ │ │ │ +B8F19 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8F19: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8F36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8F38 Length 0005 (5) │ │ │ │ +B8F3A Flags 01 (1) 'Modification' │ │ │ │ +B8F3B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8F3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8F41 Length 000B (11) │ │ │ │ +B8F43 Version 01 (1) │ │ │ │ +B8F44 UID Size 04 (4) │ │ │ │ +B8F45 UID 00000000 (0) │ │ │ │ +B8F49 GID Size 04 (4) │ │ │ │ +B8F4A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8F4E CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +B8F52 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8F53 Created OS 03 (3) 'Unix' │ │ │ │ +B8F54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8F55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8F56 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8F58 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8F5A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8F5E CRC D13EB515 (3510547733) │ │ │ │ +B8F62 Compressed Size 00001C5F (7263) │ │ │ │ +B8F66 Uncompressed Size 0000C184 (49540) │ │ │ │ +B8F6A Filename Length 001A (26) │ │ │ │ +B8F6C Extra Length 0018 (24) │ │ │ │ +B8F6E Comment Length 0000 (0) │ │ │ │ +B8F70 Disk Start 0000 (0) │ │ │ │ +B8F72 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8F74 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8F78 Local Header Offset 00033750 (210768) │ │ │ │ +B8F7C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8F7C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8F96 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8F98 Length 0005 (5) │ │ │ │ +B8F9A Flags 01 (1) 'Modification' │ │ │ │ +B8F9B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8F9F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8FA1 Length 000B (11) │ │ │ │ +B8FA3 Version 01 (1) │ │ │ │ +B8FA4 UID Size 04 (4) │ │ │ │ +B8FA5 UID 00000000 (0) │ │ │ │ +B8FA9 GID Size 04 (4) │ │ │ │ +B8FAA GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8FAE CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +B8FB2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8FB3 Created OS 03 (3) 'Unix' │ │ │ │ +B8FB4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8FB5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8FB6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8FB8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8FBA Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8FBE CRC 2BF4762B (737441323) │ │ │ │ +B8FC2 Compressed Size 000003DE (990) │ │ │ │ +B8FC6 Uncompressed Size 00000933 (2355) │ │ │ │ +B8FCA Filename Length 0012 (18) │ │ │ │ +B8FCC Extra Length 0018 (24) │ │ │ │ +B8FCE Comment Length 0000 (0) │ │ │ │ +B8FD0 Disk Start 0000 (0) │ │ │ │ +B8FD2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8FD4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8FD8 Local Header Offset 00035403 (218115) │ │ │ │ +B8FDC Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8FDC: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8FEE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8FF0 Length 0005 (5) │ │ │ │ +B8FF2 Flags 01 (1) 'Modification' │ │ │ │ +B8FF3 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B8FF7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8FF9 Length 000B (11) │ │ │ │ +B8FFB Version 01 (1) │ │ │ │ +B8FFC UID Size 04 (4) │ │ │ │ +B8FFD UID 00000000 (0) │ │ │ │ +B9001 GID Size 04 (4) │ │ │ │ +B9002 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9006 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +B900A Created Zip Spec 3D (61) '6.1' │ │ │ │ +B900B Created OS 03 (3) 'Unix' │ │ │ │ +B900C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B900D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B900E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9010 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9012 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9016 CRC A4B161AE (2763088302) │ │ │ │ +B901A Compressed Size 000001D1 (465) │ │ │ │ +B901E Uncompressed Size 0000030F (783) │ │ │ │ +B9022 Filename Length 0020 (32) │ │ │ │ +B9024 Extra Length 0018 (24) │ │ │ │ +B9026 Comment Length 0000 (0) │ │ │ │ +B9028 Disk Start 0000 (0) │ │ │ │ +B902A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B902C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9030 Local Header Offset 0003582D (219181) │ │ │ │ +B9034 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9034: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9054 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9056 Length 0005 (5) │ │ │ │ +B9058 Flags 01 (1) 'Modification' │ │ │ │ +B9059 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B905D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B905F Length 000B (11) │ │ │ │ +B9061 Version 01 (1) │ │ │ │ +B9062 UID Size 04 (4) │ │ │ │ +B9063 UID 00000000 (0) │ │ │ │ +B9067 GID Size 04 (4) │ │ │ │ +B9068 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B906C CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +B9070 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9071 Created OS 03 (3) 'Unix' │ │ │ │ +B9072 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9073 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9074 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9076 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9078 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B907C CRC 075D22ED (123544301) │ │ │ │ +B9080 Compressed Size 000017A7 (6055) │ │ │ │ +B9084 Uncompressed Size 00009D16 (40214) │ │ │ │ +B9088 Filename Length 001B (27) │ │ │ │ +B908A Extra Length 0018 (24) │ │ │ │ +B908C Comment Length 0000 (0) │ │ │ │ +B908E Disk Start 0000 (0) │ │ │ │ +B9090 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9092 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9096 Local Header Offset 00035A58 (219736) │ │ │ │ +B909A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB909A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B90B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B90B7 Length 0005 (5) │ │ │ │ +B90B9 Flags 01 (1) 'Modification' │ │ │ │ +B90BA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B90BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B90C0 Length 000B (11) │ │ │ │ +B90C2 Version 01 (1) │ │ │ │ +B90C3 UID Size 04 (4) │ │ │ │ +B90C4 UID 00000000 (0) │ │ │ │ +B90C8 GID Size 04 (4) │ │ │ │ +B90C9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B90CD CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +B90D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B90D2 Created OS 03 (3) 'Unix' │ │ │ │ +B90D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B90D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B90D5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B90D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B90D9 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B90DD CRC 1F5C05CF (526124495) │ │ │ │ +B90E1 Compressed Size 00001370 (4976) │ │ │ │ +B90E5 Uncompressed Size 00003B5F (15199) │ │ │ │ +B90E9 Filename Length 0015 (21) │ │ │ │ +B90EB Extra Length 0018 (24) │ │ │ │ +B90ED Comment Length 0000 (0) │ │ │ │ +B90EF Disk Start 0000 (0) │ │ │ │ +B90F1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B90F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B90F7 Local Header Offset 00037254 (225876) │ │ │ │ +B90FB Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB90FB: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9110 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9112 Length 0005 (5) │ │ │ │ +B9114 Flags 01 (1) 'Modification' │ │ │ │ +B9115 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9119 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B911B Length 000B (11) │ │ │ │ +B911D Version 01 (1) │ │ │ │ +B911E UID Size 04 (4) │ │ │ │ +B911F UID 00000000 (0) │ │ │ │ +B9123 GID Size 04 (4) │ │ │ │ +B9124 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9128 CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +B912C Created Zip Spec 3D (61) '6.1' │ │ │ │ +B912D Created OS 03 (3) 'Unix' │ │ │ │ +B912E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B912F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9130 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9132 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9134 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9138 CRC ED1A23A7 (3977913255) │ │ │ │ +B913C Compressed Size 00000ACC (2764) │ │ │ │ +B9140 Uncompressed Size 00002133 (8499) │ │ │ │ +B9144 Filename Length 0011 (17) │ │ │ │ +B9146 Extra Length 0018 (24) │ │ │ │ +B9148 Comment Length 0000 (0) │ │ │ │ +B914A Disk Start 0000 (0) │ │ │ │ +B914C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B914E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9152 Local Header Offset 00038613 (230931) │ │ │ │ +B9156 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9156: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9167 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9169 Length 0005 (5) │ │ │ │ +B916B Flags 01 (1) 'Modification' │ │ │ │ +B916C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9170 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9172 Length 000B (11) │ │ │ │ +B9174 Version 01 (1) │ │ │ │ +B9175 UID Size 04 (4) │ │ │ │ +B9176 UID 00000000 (0) │ │ │ │ +B917A GID Size 04 (4) │ │ │ │ +B917B GID 00000000 (0) │ │ │ │ + │ │ │ │ +B917F CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +B9183 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9184 Created OS 03 (3) 'Unix' │ │ │ │ +B9185 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9186 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9187 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9189 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B918B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B918F CRC AFD8454F (2950186319) │ │ │ │ +B9193 Compressed Size 000003FC (1020) │ │ │ │ +B9197 Uncompressed Size 00000F0A (3850) │ │ │ │ +B919B Filename Length 0014 (20) │ │ │ │ +B919D Extra Length 0018 (24) │ │ │ │ +B919F Comment Length 0000 (0) │ │ │ │ +B91A1 Disk Start 0000 (0) │ │ │ │ +B91A3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B91A5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B91A9 Local Header Offset 0003912A (233770) │ │ │ │ +B91AD Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB91AD: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B91C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B91C3 Length 0005 (5) │ │ │ │ +B91C5 Flags 01 (1) 'Modification' │ │ │ │ +B91C6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B91CA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B91CC Length 000B (11) │ │ │ │ +B91CE Version 01 (1) │ │ │ │ +B91CF UID Size 04 (4) │ │ │ │ +B91D0 UID 00000000 (0) │ │ │ │ +B91D4 GID Size 04 (4) │ │ │ │ +B91D5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B91D9 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +B91DD Created Zip Spec 3D (61) '6.1' │ │ │ │ +B91DE Created OS 03 (3) 'Unix' │ │ │ │ +B91DF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B91E0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B91E1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B91E3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B91E5 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B91E9 CRC A1F8FAEC (2717448940) │ │ │ │ +B91ED Compressed Size 0000125F (4703) │ │ │ │ +B91F1 Uncompressed Size 00003467 (13415) │ │ │ │ +B91F5 Filename Length 0014 (20) │ │ │ │ +B91F7 Extra Length 0018 (24) │ │ │ │ +B91F9 Comment Length 0000 (0) │ │ │ │ +B91FB Disk Start 0000 (0) │ │ │ │ +B91FD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B91FF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9203 Local Header Offset 00039574 (234868) │ │ │ │ +B9207 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9207: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B921B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B921D Length 0005 (5) │ │ │ │ +B921F Flags 01 (1) 'Modification' │ │ │ │ +B9220 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9224 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9226 Length 000B (11) │ │ │ │ +B9228 Version 01 (1) │ │ │ │ +B9229 UID Size 04 (4) │ │ │ │ +B922A UID 00000000 (0) │ │ │ │ +B922E GID Size 04 (4) │ │ │ │ +B922F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9233 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +B9237 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9238 Created OS 03 (3) 'Unix' │ │ │ │ +B9239 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B923A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B923B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B923D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B923F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9243 CRC F8562843 (4166395971) │ │ │ │ +B9247 Compressed Size 00000ACA (2762) │ │ │ │ +B924B Uncompressed Size 000022FD (8957) │ │ │ │ +B924F Filename Length 001B (27) │ │ │ │ +B9251 Extra Length 0018 (24) │ │ │ │ +B9253 Comment Length 0000 (0) │ │ │ │ +B9255 Disk Start 0000 (0) │ │ │ │ +B9257 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9259 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B925D Local Header Offset 0003A821 (239649) │ │ │ │ +B9261 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9261: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B927C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B927E Length 0005 (5) │ │ │ │ +B9280 Flags 01 (1) 'Modification' │ │ │ │ +B9281 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9285 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9287 Length 000B (11) │ │ │ │ +B9289 Version 01 (1) │ │ │ │ +B928A UID Size 04 (4) │ │ │ │ +B928B UID 00000000 (0) │ │ │ │ +B928F GID Size 04 (4) │ │ │ │ +B9290 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9294 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +B9298 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9299 Created OS 03 (3) 'Unix' │ │ │ │ +B929A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B929B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B929C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B929E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B92A0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B92A4 CRC A9BABEAA (2847588010) │ │ │ │ +B92A8 Compressed Size 00000C4D (3149) │ │ │ │ +B92AC Uncompressed Size 0000273A (10042) │ │ │ │ +B92B0 Filename Length 0013 (19) │ │ │ │ +B92B2 Extra Length 0018 (24) │ │ │ │ +B92B4 Comment Length 0000 (0) │ │ │ │ +B92B6 Disk Start 0000 (0) │ │ │ │ +B92B8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B92BA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B92BE Local Header Offset 0003B340 (242496) │ │ │ │ +B92C2 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB92C2: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B92D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B92D7 Length 0005 (5) │ │ │ │ +B92D9 Flags 01 (1) 'Modification' │ │ │ │ +B92DA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B92DE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B92E0 Length 000B (11) │ │ │ │ +B92E2 Version 01 (1) │ │ │ │ +B92E3 UID Size 04 (4) │ │ │ │ +B92E4 UID 00000000 (0) │ │ │ │ +B92E8 GID Size 04 (4) │ │ │ │ +B92E9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B92ED CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +B92F1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B92F2 Created OS 03 (3) 'Unix' │ │ │ │ +B92F3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B92F4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B92F5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B92F7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B92F9 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B92FD CRC 90533DAF (2421374383) │ │ │ │ +B9301 Compressed Size 00000C8F (3215) │ │ │ │ +B9305 Uncompressed Size 00003D0E (15630) │ │ │ │ +B9309 Filename Length 0014 (20) │ │ │ │ +B930B Extra Length 0018 (24) │ │ │ │ +B930D Comment Length 0000 (0) │ │ │ │ +B930F Disk Start 0000 (0) │ │ │ │ +B9311 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9313 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9317 Local Header Offset 0003BFDA (245722) │ │ │ │ +B931B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB931B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B932F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9331 Length 0005 (5) │ │ │ │ +B9333 Flags 01 (1) 'Modification' │ │ │ │ +B9334 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9338 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B933A Length 000B (11) │ │ │ │ +B933C Version 01 (1) │ │ │ │ +B933D UID Size 04 (4) │ │ │ │ +B933E UID 00000000 (0) │ │ │ │ +B9342 GID Size 04 (4) │ │ │ │ +B9343 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9347 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +B934B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B934C Created OS 03 (3) 'Unix' │ │ │ │ +B934D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B934E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B934F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9351 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9353 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9357 CRC C54076B2 (3309336242) │ │ │ │ +B935B Compressed Size 00000F44 (3908) │ │ │ │ +B935F Uncompressed Size 00003742 (14146) │ │ │ │ +B9363 Filename Length 000F (15) │ │ │ │ +B9365 Extra Length 0018 (24) │ │ │ │ +B9367 Comment Length 0000 (0) │ │ │ │ +B9369 Disk Start 0000 (0) │ │ │ │ +B936B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B936D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9371 Local Header Offset 0003CCB7 (249015) │ │ │ │ +B9375 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9375: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9384 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9386 Length 0005 (5) │ │ │ │ +B9388 Flags 01 (1) 'Modification' │ │ │ │ +B9389 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B938D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B938F Length 000B (11) │ │ │ │ +B9391 Version 01 (1) │ │ │ │ +B9392 UID Size 04 (4) │ │ │ │ +B9393 UID 00000000 (0) │ │ │ │ +B9397 GID Size 04 (4) │ │ │ │ +B9398 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B939C CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +B93A0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B93A1 Created OS 03 (3) 'Unix' │ │ │ │ +B93A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B93A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B93A4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B93A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B93A8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B93AC CRC 5282A93D (1384294717) │ │ │ │ +B93B0 Compressed Size 000006B9 (1721) │ │ │ │ +B93B4 Uncompressed Size 00001A77 (6775) │ │ │ │ +B93B8 Filename Length 000F (15) │ │ │ │ +B93BA Extra Length 0018 (24) │ │ │ │ +B93BC Comment Length 0000 (0) │ │ │ │ +B93BE Disk Start 0000 (0) │ │ │ │ +B93C0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B93C2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B93C6 Local Header Offset 0003DC44 (252996) │ │ │ │ +B93CA Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB93CA: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B93D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B93DB Length 0005 (5) │ │ │ │ +B93DD Flags 01 (1) 'Modification' │ │ │ │ +B93DE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B93E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B93E4 Length 000B (11) │ │ │ │ +B93E6 Version 01 (1) │ │ │ │ +B93E7 UID Size 04 (4) │ │ │ │ +B93E8 UID 00000000 (0) │ │ │ │ +B93EC GID Size 04 (4) │ │ │ │ +B93ED GID 00000000 (0) │ │ │ │ + │ │ │ │ +B93F1 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ +B93F5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B93F6 Created OS 03 (3) 'Unix' │ │ │ │ +B93F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B93F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B93F9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B93FB Compression Method 0008 (8) 'Deflated' │ │ │ │ +B93FD Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9401 CRC FD7C9D36 (4252802358) │ │ │ │ +B9405 Compressed Size 00001A42 (6722) │ │ │ │ +B9409 Uncompressed Size 000064F8 (25848) │ │ │ │ +B940D Filename Length 0013 (19) │ │ │ │ +B940F Extra Length 0018 (24) │ │ │ │ +B9411 Comment Length 0000 (0) │ │ │ │ +B9413 Disk Start 0000 (0) │ │ │ │ +B9415 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9417 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B941B Local Header Offset 0003E346 (254790) │ │ │ │ +B941F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB941F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9432 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9434 Length 0005 (5) │ │ │ │ +B9436 Flags 01 (1) 'Modification' │ │ │ │ +B9437 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B943B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B943D Length 000B (11) │ │ │ │ +B943F Version 01 (1) │ │ │ │ +B9440 UID Size 04 (4) │ │ │ │ +B9441 UID 00000000 (0) │ │ │ │ +B9445 GID Size 04 (4) │ │ │ │ +B9446 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B944A CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +B944E Created Zip Spec 3D (61) '6.1' │ │ │ │ +B944F Created OS 03 (3) 'Unix' │ │ │ │ +B9450 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9451 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9452 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9454 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9456 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B945A CRC 5D9A5F08 (1570397960) │ │ │ │ +B945E Compressed Size 000009A4 (2468) │ │ │ │ +B9462 Uncompressed Size 00001B62 (7010) │ │ │ │ +B9466 Filename Length 0010 (16) │ │ │ │ +B9468 Extra Length 0018 (24) │ │ │ │ +B946A Comment Length 0000 (0) │ │ │ │ +B946C Disk Start 0000 (0) │ │ │ │ +B946E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9470 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9474 Local Header Offset 0003FDD5 (261589) │ │ │ │ +B9478 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9478: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9488 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B948A Length 0005 (5) │ │ │ │ +B948C Flags 01 (1) 'Modification' │ │ │ │ +B948D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9491 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9493 Length 000B (11) │ │ │ │ +B9495 Version 01 (1) │ │ │ │ +B9496 UID Size 04 (4) │ │ │ │ +B9497 UID 00000000 (0) │ │ │ │ +B949B GID Size 04 (4) │ │ │ │ +B949C GID 00000000 (0) │ │ │ │ + │ │ │ │ +B94A0 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +B94A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B94A5 Created OS 03 (3) 'Unix' │ │ │ │ +B94A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B94A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B94A8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B94AA Compression Method 0008 (8) 'Deflated' │ │ │ │ +B94AC Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B94B0 CRC 2F07E577 (789046647) │ │ │ │ +B94B4 Compressed Size 000006B5 (1717) │ │ │ │ +B94B8 Uncompressed Size 00001563 (5475) │ │ │ │ +B94BC Filename Length 0012 (18) │ │ │ │ +B94BE Extra Length 0018 (24) │ │ │ │ +B94C0 Comment Length 0000 (0) │ │ │ │ +B94C2 Disk Start 0000 (0) │ │ │ │ +B94C4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B94C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B94CA Local Header Offset 000407C3 (264131) │ │ │ │ +B94CE Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB94CE: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B94E0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B94E2 Length 0005 (5) │ │ │ │ +B94E4 Flags 01 (1) 'Modification' │ │ │ │ +B94E5 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B94E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B94EB Length 000B (11) │ │ │ │ +B94ED Version 01 (1) │ │ │ │ +B94EE UID Size 04 (4) │ │ │ │ +B94EF UID 00000000 (0) │ │ │ │ +B94F3 GID Size 04 (4) │ │ │ │ +B94F4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B94F8 CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +B94FC Created Zip Spec 3D (61) '6.1' │ │ │ │ +B94FD Created OS 03 (3) 'Unix' │ │ │ │ +B94FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B94FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9500 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9502 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9504 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9508 CRC F55AF488 (4116378760) │ │ │ │ +B950C Compressed Size 00002D67 (11623) │ │ │ │ +B9510 Uncompressed Size 0000D07C (53372) │ │ │ │ +B9514 Filename Length 0010 (16) │ │ │ │ +B9516 Extra Length 0018 (24) │ │ │ │ +B9518 Comment Length 0000 (0) │ │ │ │ +B951A Disk Start 0000 (0) │ │ │ │ +B951C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B951E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9522 Local Header Offset 00040EC4 (265924) │ │ │ │ +B9526 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9526: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9536 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9538 Length 0005 (5) │ │ │ │ +B953A Flags 01 (1) 'Modification' │ │ │ │ +B953B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B953F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9541 Length 000B (11) │ │ │ │ +B9543 Version 01 (1) │ │ │ │ +B9544 UID Size 04 (4) │ │ │ │ +B9545 UID 00000000 (0) │ │ │ │ +B9549 GID Size 04 (4) │ │ │ │ +B954A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B954E CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +B9552 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9553 Created OS 03 (3) 'Unix' │ │ │ │ +B9554 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9555 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9556 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9558 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B955A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B955E CRC 992A52CF (2569687759) │ │ │ │ +B9562 Compressed Size 00001E87 (7815) │ │ │ │ +B9566 Uncompressed Size 00009AA8 (39592) │ │ │ │ +B956A Filename Length 0012 (18) │ │ │ │ +B956C Extra Length 0018 (24) │ │ │ │ +B956E Comment Length 0000 (0) │ │ │ │ +B9570 Disk Start 0000 (0) │ │ │ │ +B9572 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9574 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9578 Local Header Offset 00043C75 (277621) │ │ │ │ +B957C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB957C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B958E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9590 Length 0005 (5) │ │ │ │ +B9592 Flags 01 (1) 'Modification' │ │ │ │ +B9593 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9597 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9599 Length 000B (11) │ │ │ │ +B959B Version 01 (1) │ │ │ │ +B959C UID Size 04 (4) │ │ │ │ +B959D UID 00000000 (0) │ │ │ │ +B95A1 GID Size 04 (4) │ │ │ │ +B95A2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B95A6 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +B95AA Created Zip Spec 3D (61) '6.1' │ │ │ │ +B95AB Created OS 03 (3) 'Unix' │ │ │ │ +B95AC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B95AD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B95AE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B95B0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B95B2 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B95B6 CRC 82702DF7 (2188389879) │ │ │ │ +B95BA Compressed Size 0000146E (5230) │ │ │ │ +B95BE Uncompressed Size 00007ACD (31437) │ │ │ │ +B95C2 Filename Length 0018 (24) │ │ │ │ +B95C4 Extra Length 0018 (24) │ │ │ │ +B95C6 Comment Length 0000 (0) │ │ │ │ +B95C8 Disk Start 0000 (0) │ │ │ │ +B95CA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B95CC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B95D0 Local Header Offset 00045B48 (285512) │ │ │ │ +B95D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB95D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B95EC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B95EE Length 0005 (5) │ │ │ │ +B95F0 Flags 01 (1) 'Modification' │ │ │ │ +B95F1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B95F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B95F7 Length 000B (11) │ │ │ │ +B95F9 Version 01 (1) │ │ │ │ +B95FA UID Size 04 (4) │ │ │ │ +B95FB UID 00000000 (0) │ │ │ │ +B95FF GID Size 04 (4) │ │ │ │ +B9600 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9604 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +B9608 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9609 Created OS 03 (3) 'Unix' │ │ │ │ +B960A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B960B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B960C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B960E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9610 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9614 CRC 347EA9D0 (880716240) │ │ │ │ +B9618 Compressed Size 00002148 (8520) │ │ │ │ +B961C Uncompressed Size 0000CCBC (52412) │ │ │ │ +B9620 Filename Length 001F (31) │ │ │ │ +B9622 Extra Length 0018 (24) │ │ │ │ +B9624 Comment Length 0000 (0) │ │ │ │ +B9626 Disk Start 0000 (0) │ │ │ │ +B9628 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B962A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B962E Local Header Offset 00047008 (290824) │ │ │ │ +B9632 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9632: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9651 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9653 Length 0005 (5) │ │ │ │ +B9655 Flags 01 (1) 'Modification' │ │ │ │ +B9656 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B965A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B965C Length 000B (11) │ │ │ │ +B965E Version 01 (1) │ │ │ │ +B965F UID Size 04 (4) │ │ │ │ +B9660 UID 00000000 (0) │ │ │ │ +B9664 GID Size 04 (4) │ │ │ │ +B9665 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9669 CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +B966D Created Zip Spec 3D (61) '6.1' │ │ │ │ +B966E Created OS 03 (3) 'Unix' │ │ │ │ +B966F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9670 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9671 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9673 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9675 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9679 CRC 80CE55AF (2161005999) │ │ │ │ +B967D Compressed Size 000003F5 (1013) │ │ │ │ +B9681 Uncompressed Size 000008A1 (2209) │ │ │ │ +B9685 Filename Length 001E (30) │ │ │ │ +B9687 Extra Length 0018 (24) │ │ │ │ +B9689 Comment Length 0000 (0) │ │ │ │ +B968B Disk Start 0000 (0) │ │ │ │ +B968D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B968F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9693 Local Header Offset 000491A9 (299433) │ │ │ │ +B9697 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9697: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B96B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B96B7 Length 0005 (5) │ │ │ │ +B96B9 Flags 01 (1) 'Modification' │ │ │ │ +B96BA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B96BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B96C0 Length 000B (11) │ │ │ │ +B96C2 Version 01 (1) │ │ │ │ +B96C3 UID Size 04 (4) │ │ │ │ +B96C4 UID 00000000 (0) │ │ │ │ +B96C8 GID Size 04 (4) │ │ │ │ +B96C9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B96CD CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +B96D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B96D2 Created OS 03 (3) 'Unix' │ │ │ │ +B96D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B96D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B96D5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B96D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B96D9 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B96DD CRC DF319C26 (3744570406) │ │ │ │ +B96E1 Compressed Size 00004308 (17160) │ │ │ │ +B96E5 Uncompressed Size 0000DAC9 (56009) │ │ │ │ +B96E9 Filename Length 0013 (19) │ │ │ │ +B96EB Extra Length 0018 (24) │ │ │ │ +B96ED Comment Length 0000 (0) │ │ │ │ +B96EF Disk Start 0000 (0) │ │ │ │ +B96F1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B96F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B96F7 Local Header Offset 000495F6 (300534) │ │ │ │ +B96FB Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB96FB: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B970E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9710 Length 0005 (5) │ │ │ │ +B9712 Flags 01 (1) 'Modification' │ │ │ │ +B9713 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9717 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9719 Length 000B (11) │ │ │ │ +B971B Version 01 (1) │ │ │ │ +B971C UID Size 04 (4) │ │ │ │ +B971D UID 00000000 (0) │ │ │ │ +B9721 GID Size 04 (4) │ │ │ │ +B9722 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9726 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +B972A Created Zip Spec 3D (61) '6.1' │ │ │ │ +B972B Created OS 03 (3) 'Unix' │ │ │ │ +B972C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B972D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B972E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9730 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9732 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9736 CRC 8C04B533 (2349118771) │ │ │ │ +B973A Compressed Size 000026C0 (9920) │ │ │ │ +B973E Uncompressed Size 00006E43 (28227) │ │ │ │ +B9742 Filename Length 0019 (25) │ │ │ │ +B9744 Extra Length 0018 (24) │ │ │ │ +B9746 Comment Length 0000 (0) │ │ │ │ +B9748 Disk Start 0000 (0) │ │ │ │ +B974A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B974C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9750 Local Header Offset 0004D94B (317771) │ │ │ │ +B9754 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9754: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B976D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B976F Length 0005 (5) │ │ │ │ +B9771 Flags 01 (1) 'Modification' │ │ │ │ +B9772 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9776 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9778 Length 000B (11) │ │ │ │ +B977A Version 01 (1) │ │ │ │ +B977B UID Size 04 (4) │ │ │ │ +B977C UID 00000000 (0) │ │ │ │ +B9780 GID Size 04 (4) │ │ │ │ +B9781 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9785 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +B9789 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B978A Created OS 03 (3) 'Unix' │ │ │ │ +B978B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B978C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B978D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B978F Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9791 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9795 CRC C06E9EF2 (3228475122) │ │ │ │ +B9799 Compressed Size 0000273D (10045) │ │ │ │ +B979D Uncompressed Size 00008B81 (35713) │ │ │ │ +B97A1 Filename Length 0019 (25) │ │ │ │ +B97A3 Extra Length 0018 (24) │ │ │ │ +B97A5 Comment Length 0000 (0) │ │ │ │ +B97A7 Disk Start 0000 (0) │ │ │ │ +B97A9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B97AB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B97AF Local Header Offset 0005005E (327774) │ │ │ │ +B97B3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB97B3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B97CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B97CE Length 0005 (5) │ │ │ │ +B97D0 Flags 01 (1) 'Modification' │ │ │ │ +B97D1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B97D5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B97D7 Length 000B (11) │ │ │ │ +B97D9 Version 01 (1) │ │ │ │ +B97DA UID Size 04 (4) │ │ │ │ +B97DB UID 00000000 (0) │ │ │ │ +B97DF GID Size 04 (4) │ │ │ │ +B97E0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B97E4 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +B97E8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B97E9 Created OS 03 (3) 'Unix' │ │ │ │ +B97EA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B97EB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B97EC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B97EE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B97F0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B97F4 CRC F07DFB90 (4034788240) │ │ │ │ +B97F8 Compressed Size 00000ECA (3786) │ │ │ │ +B97FC Uncompressed Size 000053BD (21437) │ │ │ │ +B9800 Filename Length 0021 (33) │ │ │ │ +B9802 Extra Length 0018 (24) │ │ │ │ +B9804 Comment Length 0000 (0) │ │ │ │ +B9806 Disk Start 0000 (0) │ │ │ │ +B9808 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B980A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B980E Local Header Offset 000527EE (337902) │ │ │ │ +B9812 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9812: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9833 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9835 Length 0005 (5) │ │ │ │ +B9837 Flags 01 (1) 'Modification' │ │ │ │ +B9838 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B983C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B983E Length 000B (11) │ │ │ │ +B9840 Version 01 (1) │ │ │ │ +B9841 UID Size 04 (4) │ │ │ │ +B9842 UID 00000000 (0) │ │ │ │ +B9846 GID Size 04 (4) │ │ │ │ +B9847 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B984B CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +B984F Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9850 Created OS 03 (3) 'Unix' │ │ │ │ +B9851 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9852 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9853 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9855 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9857 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B985B CRC 7E4C0584 (2118911364) │ │ │ │ +B985F Compressed Size 00000534 (1332) │ │ │ │ +B9863 Uncompressed Size 00000C94 (3220) │ │ │ │ +B9867 Filename Length 0017 (23) │ │ │ │ +B9869 Extra Length 0018 (24) │ │ │ │ +B986B Comment Length 0000 (0) │ │ │ │ +B986D Disk Start 0000 (0) │ │ │ │ +B986F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9871 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9875 Local Header Offset 00053713 (341779) │ │ │ │ +B9879 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9879: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9890 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9892 Length 0005 (5) │ │ │ │ +B9894 Flags 01 (1) 'Modification' │ │ │ │ +B9895 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9899 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B989B Length 000B (11) │ │ │ │ +B989D Version 01 (1) │ │ │ │ +B989E UID Size 04 (4) │ │ │ │ +B989F UID 00000000 (0) │ │ │ │ +B98A3 GID Size 04 (4) │ │ │ │ +B98A4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B98A8 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +B98AC Created Zip Spec 3D (61) '6.1' │ │ │ │ +B98AD Created OS 03 (3) 'Unix' │ │ │ │ +B98AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B98AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B98B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B98B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B98B4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B98B8 CRC 76893EF6 (1988706038) │ │ │ │ +B98BC Compressed Size 00000465 (1125) │ │ │ │ +B98C0 Uncompressed Size 0000092F (2351) │ │ │ │ +B98C4 Filename Length 001B (27) │ │ │ │ +B98C6 Extra Length 0018 (24) │ │ │ │ +B98C8 Comment Length 0000 (0) │ │ │ │ +B98CA Disk Start 0000 (0) │ │ │ │ +B98CC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B98CE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B98D2 Local Header Offset 00053C98 (343192) │ │ │ │ +B98D6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB98D6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B98F1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B98F3 Length 0005 (5) │ │ │ │ +B98F5 Flags 01 (1) 'Modification' │ │ │ │ +B98F6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B98FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B98FC Length 000B (11) │ │ │ │ +B98FE Version 01 (1) │ │ │ │ +B98FF UID Size 04 (4) │ │ │ │ +B9900 UID 00000000 (0) │ │ │ │ +B9904 GID Size 04 (4) │ │ │ │ +B9905 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9909 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +B990D Created Zip Spec 3D (61) '6.1' │ │ │ │ +B990E Created OS 03 (3) 'Unix' │ │ │ │ +B990F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9910 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9911 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9913 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9915 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9919 CRC 14A45306 (346313478) │ │ │ │ +B991D Compressed Size 000016F3 (5875) │ │ │ │ +B9921 Uncompressed Size 00007A6B (31339) │ │ │ │ +B9925 Filename Length 001F (31) │ │ │ │ +B9927 Extra Length 0018 (24) │ │ │ │ +B9929 Comment Length 0000 (0) │ │ │ │ +B992B Disk Start 0000 (0) │ │ │ │ +B992D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B992F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9933 Local Header Offset 00054152 (344402) │ │ │ │ +B9937 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9937: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9956 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9958 Length 0005 (5) │ │ │ │ +B995A Flags 01 (1) 'Modification' │ │ │ │ +B995B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B995F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9961 Length 000B (11) │ │ │ │ +B9963 Version 01 (1) │ │ │ │ +B9964 UID Size 04 (4) │ │ │ │ +B9965 UID 00000000 (0) │ │ │ │ +B9969 GID Size 04 (4) │ │ │ │ +B996A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B996E CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +B9972 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9973 Created OS 03 (3) 'Unix' │ │ │ │ +B9974 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9975 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9976 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9978 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B997A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B997E CRC 5FD9B2F8 (1608102648) │ │ │ │ +B9982 Compressed Size 00004154 (16724) │ │ │ │ +B9986 Uncompressed Size 0001D15D (119133) │ │ │ │ +B998A Filename Length 0010 (16) │ │ │ │ +B998C Extra Length 0018 (24) │ │ │ │ +B998E Comment Length 0000 (0) │ │ │ │ +B9990 Disk Start 0000 (0) │ │ │ │ +B9992 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9994 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9998 Local Header Offset 0005589E (350366) │ │ │ │ +B999C Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB999C: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B99AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B99AE Length 0005 (5) │ │ │ │ +B99B0 Flags 01 (1) 'Modification' │ │ │ │ +B99B1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B99B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B99B7 Length 000B (11) │ │ │ │ +B99B9 Version 01 (1) │ │ │ │ +B99BA UID Size 04 (4) │ │ │ │ +B99BB UID 00000000 (0) │ │ │ │ +B99BF GID Size 04 (4) │ │ │ │ +B99C0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B99C4 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +B99C8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B99C9 Created OS 03 (3) 'Unix' │ │ │ │ +B99CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B99CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B99CC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B99CE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B99D0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B99D4 CRC 5C6B0F1E (1550520094) │ │ │ │ +B99D8 Compressed Size 00000A93 (2707) │ │ │ │ +B99DC Uncompressed Size 00002103 (8451) │ │ │ │ +B99E0 Filename Length 0014 (20) │ │ │ │ +B99E2 Extra Length 0018 (24) │ │ │ │ +B99E4 Comment Length 0000 (0) │ │ │ │ +B99E6 Disk Start 0000 (0) │ │ │ │ +B99E8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B99EA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B99EE Local Header Offset 00059A3C (367164) │ │ │ │ +B99F2 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB99F2: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9A06 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9A08 Length 0005 (5) │ │ │ │ +B9A0A Flags 01 (1) 'Modification' │ │ │ │ +B9A0B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9A0F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9A11 Length 000B (11) │ │ │ │ +B9A13 Version 01 (1) │ │ │ │ +B9A14 UID Size 04 (4) │ │ │ │ +B9A15 UID 00000000 (0) │ │ │ │ +B9A19 GID Size 04 (4) │ │ │ │ +B9A1A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9A1E CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +B9A22 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9A23 Created OS 03 (3) 'Unix' │ │ │ │ +B9A24 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9A25 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9A26 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9A28 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9A2A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9A2E CRC B65A8F9E (3059388318) │ │ │ │ +B9A32 Compressed Size 0000B53F (46399) │ │ │ │ +B9A36 Uncompressed Size 000418F9 (268537) │ │ │ │ +B9A3A Filename Length 0017 (23) │ │ │ │ +B9A3C Extra Length 0018 (24) │ │ │ │ +B9A3E Comment Length 0000 (0) │ │ │ │ +B9A40 Disk Start 0000 (0) │ │ │ │ +B9A42 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9A44 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9A48 Local Header Offset 0005A51D (369949) │ │ │ │ +B9A4C Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9A4C: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9A63 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9A65 Length 0005 (5) │ │ │ │ +B9A67 Flags 01 (1) 'Modification' │ │ │ │ +B9A68 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9A6C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9A6E Length 000B (11) │ │ │ │ +B9A70 Version 01 (1) │ │ │ │ +B9A71 UID Size 04 (4) │ │ │ │ +B9A72 UID 00000000 (0) │ │ │ │ +B9A76 GID Size 04 (4) │ │ │ │ +B9A77 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9A7B CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +B9A7F Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9A80 Created OS 03 (3) 'Unix' │ │ │ │ +B9A81 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9A82 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9A83 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9A85 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9A87 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9A8B CRC ED064A2F (3976612399) │ │ │ │ +B9A8F Compressed Size 000003FF (1023) │ │ │ │ +B9A93 Uncompressed Size 0000093B (2363) │ │ │ │ +B9A97 Filename Length 0013 (19) │ │ │ │ +B9A99 Extra Length 0018 (24) │ │ │ │ +B9A9B Comment Length 0000 (0) │ │ │ │ +B9A9D Disk Start 0000 (0) │ │ │ │ +B9A9F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9AA1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9AA5 Local Header Offset 00065AAD (416429) │ │ │ │ +B9AA9 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9AA9: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9ABC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9ABE Length 0005 (5) │ │ │ │ +B9AC0 Flags 01 (1) 'Modification' │ │ │ │ +B9AC1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9AC5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9AC7 Length 000B (11) │ │ │ │ +B9AC9 Version 01 (1) │ │ │ │ +B9ACA UID Size 04 (4) │ │ │ │ +B9ACB UID 00000000 (0) │ │ │ │ +B9ACF GID Size 04 (4) │ │ │ │ +B9AD0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9AD4 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +B9AD8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9AD9 Created OS 03 (3) 'Unix' │ │ │ │ +B9ADA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9ADB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9ADC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9ADE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9AE0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9AE4 CRC 5E7E8B5A (1585351514) │ │ │ │ +B9AE8 Compressed Size 000014DA (5338) │ │ │ │ +B9AEC Uncompressed Size 00006890 (26768) │ │ │ │ +B9AF0 Filename Length 0012 (18) │ │ │ │ +B9AF2 Extra Length 0018 (24) │ │ │ │ +B9AF4 Comment Length 0000 (0) │ │ │ │ +B9AF6 Disk Start 0000 (0) │ │ │ │ +B9AF8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9AFA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9AFE Local Header Offset 00065EF9 (417529) │ │ │ │ +B9B02 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9B02: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9B14 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9B16 Length 0005 (5) │ │ │ │ +B9B18 Flags 01 (1) 'Modification' │ │ │ │ +B9B19 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9B1D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9B1F Length 000B (11) │ │ │ │ +B9B21 Version 01 (1) │ │ │ │ +B9B22 UID Size 04 (4) │ │ │ │ +B9B23 UID 00000000 (0) │ │ │ │ +B9B27 GID Size 04 (4) │ │ │ │ +B9B28 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9B2C CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +B9B30 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9B31 Created OS 03 (3) 'Unix' │ │ │ │ +B9B32 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9B33 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9B34 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9B36 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9B38 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9B3C CRC 399ACCCF (966446287) │ │ │ │ +B9B40 Compressed Size 000011E6 (4582) │ │ │ │ +B9B44 Uncompressed Size 000040F8 (16632) │ │ │ │ +B9B48 Filename Length 0012 (18) │ │ │ │ +B9B4A Extra Length 0018 (24) │ │ │ │ +B9B4C Comment Length 0000 (0) │ │ │ │ +B9B4E Disk Start 0000 (0) │ │ │ │ +B9B50 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9B52 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9B56 Local Header Offset 0006741F (422943) │ │ │ │ +B9B5A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9B5A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9B6C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9B6E Length 0005 (5) │ │ │ │ +B9B70 Flags 01 (1) 'Modification' │ │ │ │ +B9B71 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9B75 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9B77 Length 000B (11) │ │ │ │ +B9B79 Version 01 (1) │ │ │ │ +B9B7A UID Size 04 (4) │ │ │ │ +B9B7B UID 00000000 (0) │ │ │ │ +B9B7F GID Size 04 (4) │ │ │ │ +B9B80 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9B84 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +B9B88 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9B89 Created OS 03 (3) 'Unix' │ │ │ │ +B9B8A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9B8B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9B8C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9B8E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9B90 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9B94 CRC B73C2316 (3074171670) │ │ │ │ +B9B98 Compressed Size 000009D8 (2520) │ │ │ │ +B9B9C Uncompressed Size 00003527 (13607) │ │ │ │ +B9BA0 Filename Length 0019 (25) │ │ │ │ +B9BA2 Extra Length 0018 (24) │ │ │ │ +B9BA4 Comment Length 0000 (0) │ │ │ │ +B9BA6 Disk Start 0000 (0) │ │ │ │ +B9BA8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9BAA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9BAE Local Header Offset 00068651 (427601) │ │ │ │ +B9BB2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9BB2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9BCB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9BCD Length 0005 (5) │ │ │ │ +B9BCF Flags 01 (1) 'Modification' │ │ │ │ +B9BD0 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9BD4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9BD6 Length 000B (11) │ │ │ │ +B9BD8 Version 01 (1) │ │ │ │ +B9BD9 UID Size 04 (4) │ │ │ │ +B9BDA UID 00000000 (0) │ │ │ │ +B9BDE GID Size 04 (4) │ │ │ │ +B9BDF GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9BE3 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +B9BE7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9BE8 Created OS 03 (3) 'Unix' │ │ │ │ +B9BE9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9BEA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9BEB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9BED Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9BEF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9BF3 CRC 708D141A (1888293914) │ │ │ │ +B9BF7 Compressed Size 000018B2 (6322) │ │ │ │ +B9BFB Uncompressed Size 0000A676 (42614) │ │ │ │ +B9BFF Filename Length 0019 (25) │ │ │ │ +B9C01 Extra Length 0018 (24) │ │ │ │ +B9C03 Comment Length 0000 (0) │ │ │ │ +B9C05 Disk Start 0000 (0) │ │ │ │ +B9C07 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9C09 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9C0D Local Header Offset 0006907C (430204) │ │ │ │ +B9C11 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9C11: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9C2A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9C2C Length 0005 (5) │ │ │ │ +B9C2E Flags 01 (1) 'Modification' │ │ │ │ +B9C2F Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9C33 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9C35 Length 000B (11) │ │ │ │ +B9C37 Version 01 (1) │ │ │ │ +B9C38 UID Size 04 (4) │ │ │ │ +B9C39 UID 00000000 (0) │ │ │ │ +B9C3D GID Size 04 (4) │ │ │ │ +B9C3E GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9C42 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +B9C46 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9C47 Created OS 03 (3) 'Unix' │ │ │ │ +B9C48 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9C49 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9C4A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9C4C Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9C4E Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9C52 CRC 28DDA60C (685614604) │ │ │ │ +B9C56 Compressed Size 0000177A (6010) │ │ │ │ +B9C5A Uncompressed Size 0000472A (18218) │ │ │ │ +B9C5E Filename Length 0014 (20) │ │ │ │ +B9C60 Extra Length 0018 (24) │ │ │ │ +B9C62 Comment Length 0000 (0) │ │ │ │ +B9C64 Disk Start 0000 (0) │ │ │ │ +B9C66 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9C68 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9C6C Local Header Offset 0006A981 (436609) │ │ │ │ +B9C70 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9C70: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9C84 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9C86 Length 0005 (5) │ │ │ │ +B9C88 Flags 01 (1) 'Modification' │ │ │ │ +B9C89 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9C8D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9C8F Length 000B (11) │ │ │ │ +B9C91 Version 01 (1) │ │ │ │ +B9C92 UID Size 04 (4) │ │ │ │ +B9C93 UID 00000000 (0) │ │ │ │ +B9C97 GID Size 04 (4) │ │ │ │ +B9C98 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9C9C CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +B9CA0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9CA1 Created OS 03 (3) 'Unix' │ │ │ │ +B9CA2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9CA3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9CA4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9CA6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9CA8 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9CAC CRC AAF8364A (2868393546) │ │ │ │ +B9CB0 Compressed Size 00000408 (1032) │ │ │ │ +B9CB4 Uncompressed Size 00000823 (2083) │ │ │ │ +B9CB8 Filename Length 001C (28) │ │ │ │ +B9CBA Extra Length 0018 (24) │ │ │ │ +B9CBC Comment Length 0000 (0) │ │ │ │ +B9CBE Disk Start 0000 (0) │ │ │ │ +B9CC0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9CC2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9CC6 Local Header Offset 0006C149 (442697) │ │ │ │ +B9CCA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9CCA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9CE6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9CE8 Length 0005 (5) │ │ │ │ +B9CEA Flags 01 (1) 'Modification' │ │ │ │ +B9CEB Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9CEF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9CF1 Length 000B (11) │ │ │ │ +B9CF3 Version 01 (1) │ │ │ │ +B9CF4 UID Size 04 (4) │ │ │ │ +B9CF5 UID 00000000 (0) │ │ │ │ +B9CF9 GID Size 04 (4) │ │ │ │ +B9CFA GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9CFE CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +B9D02 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9D03 Created OS 03 (3) 'Unix' │ │ │ │ +B9D04 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9D05 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9D06 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9D08 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9D0A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9D0E CRC 2B886ACB (730360523) │ │ │ │ +B9D12 Compressed Size 000024C1 (9409) │ │ │ │ +B9D16 Uncompressed Size 0000B659 (46681) │ │ │ │ +B9D1A Filename Length 001F (31) │ │ │ │ +B9D1C Extra Length 0018 (24) │ │ │ │ +B9D1E Comment Length 0000 (0) │ │ │ │ +B9D20 Disk Start 0000 (0) │ │ │ │ +B9D22 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9D24 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9D28 Local Header Offset 0006C5A7 (443815) │ │ │ │ +B9D2C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9D2C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9D4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9D4D Length 0005 (5) │ │ │ │ +B9D4F Flags 01 (1) 'Modification' │ │ │ │ +B9D50 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9D54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9D56 Length 000B (11) │ │ │ │ +B9D58 Version 01 (1) │ │ │ │ +B9D59 UID Size 04 (4) │ │ │ │ +B9D5A UID 00000000 (0) │ │ │ │ +B9D5E GID Size 04 (4) │ │ │ │ +B9D5F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9D63 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +B9D67 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9D68 Created OS 03 (3) 'Unix' │ │ │ │ +B9D69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9D6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9D6B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9D6D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9D6F Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9D73 CRC 0CE5F304 (216396548) │ │ │ │ +B9D77 Compressed Size 00000E76 (3702) │ │ │ │ +B9D7B Uncompressed Size 000052D7 (21207) │ │ │ │ +B9D7F Filename Length 001F (31) │ │ │ │ +B9D81 Extra Length 0018 (24) │ │ │ │ +B9D83 Comment Length 0000 (0) │ │ │ │ +B9D85 Disk Start 0000 (0) │ │ │ │ +B9D87 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9D89 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9D8D Local Header Offset 0006EAC1 (453313) │ │ │ │ +B9D91 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9D91: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9DB0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9DB2 Length 0005 (5) │ │ │ │ +B9DB4 Flags 01 (1) 'Modification' │ │ │ │ +B9DB5 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9DB9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9DBB Length 000B (11) │ │ │ │ +B9DBD Version 01 (1) │ │ │ │ +B9DBE UID Size 04 (4) │ │ │ │ +B9DBF UID 00000000 (0) │ │ │ │ +B9DC3 GID Size 04 (4) │ │ │ │ +B9DC4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9DC8 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +B9DCC Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9DCD Created OS 03 (3) 'Unix' │ │ │ │ +B9DCE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9DCF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9DD0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9DD2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9DD4 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9DD8 CRC 995DD4D2 (2573063378) │ │ │ │ +B9DDC Compressed Size 00000A42 (2626) │ │ │ │ +B9DE0 Uncompressed Size 00002478 (9336) │ │ │ │ +B9DE4 Filename Length 0013 (19) │ │ │ │ +B9DE6 Extra Length 0018 (24) │ │ │ │ +B9DE8 Comment Length 0000 (0) │ │ │ │ +B9DEA Disk Start 0000 (0) │ │ │ │ +B9DEC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9DEE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9DF2 Local Header Offset 0006F990 (457104) │ │ │ │ +B9DF6 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9DF6: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9E09 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9E0B Length 0005 (5) │ │ │ │ +B9E0D Flags 01 (1) 'Modification' │ │ │ │ +B9E0E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9E12 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9E14 Length 000B (11) │ │ │ │ +B9E16 Version 01 (1) │ │ │ │ +B9E17 UID Size 04 (4) │ │ │ │ +B9E18 UID 00000000 (0) │ │ │ │ +B9E1C GID Size 04 (4) │ │ │ │ +B9E1D GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9E21 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +B9E25 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9E26 Created OS 03 (3) 'Unix' │ │ │ │ +B9E27 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9E28 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9E29 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9E2B Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9E2D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9E31 CRC 7D4C9201 (2102170113) │ │ │ │ +B9E35 Compressed Size 00002549 (9545) │ │ │ │ +B9E39 Uncompressed Size 0000B9C7 (47559) │ │ │ │ +B9E3D Filename Length 0019 (25) │ │ │ │ +B9E3F Extra Length 0018 (24) │ │ │ │ +B9E41 Comment Length 0000 (0) │ │ │ │ +B9E43 Disk Start 0000 (0) │ │ │ │ +B9E45 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9E47 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9E4B Local Header Offset 0007041F (459807) │ │ │ │ +B9E4F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9E4F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9E68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9E6A Length 0005 (5) │ │ │ │ +B9E6C Flags 01 (1) 'Modification' │ │ │ │ +B9E6D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9E71 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9E73 Length 000B (11) │ │ │ │ +B9E75 Version 01 (1) │ │ │ │ +B9E76 UID Size 04 (4) │ │ │ │ +B9E77 UID 00000000 (0) │ │ │ │ +B9E7B GID Size 04 (4) │ │ │ │ +B9E7C GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9E80 CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +B9E84 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9E85 Created OS 03 (3) 'Unix' │ │ │ │ +B9E86 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9E87 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9E88 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9E8A Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9E8C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9E90 CRC 0883F700 (142866176) │ │ │ │ +B9E94 Compressed Size 00000EF8 (3832) │ │ │ │ +B9E98 Uncompressed Size 00003A2A (14890) │ │ │ │ +B9E9C Filename Length 0024 (36) │ │ │ │ +B9E9E Extra Length 0018 (24) │ │ │ │ +B9EA0 Comment Length 0000 (0) │ │ │ │ +B9EA2 Disk Start 0000 (0) │ │ │ │ +B9EA4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9EA6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9EAA Local Header Offset 000729BB (469435) │ │ │ │ +B9EAE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9EAE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9ED2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9ED4 Length 0005 (5) │ │ │ │ +B9ED6 Flags 01 (1) 'Modification' │ │ │ │ +B9ED7 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9EDB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9EDD Length 000B (11) │ │ │ │ +B9EDF Version 01 (1) │ │ │ │ +B9EE0 UID Size 04 (4) │ │ │ │ +B9EE1 UID 00000000 (0) │ │ │ │ +B9EE5 GID Size 04 (4) │ │ │ │ +B9EE6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9EEA CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +B9EEE Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9EEF Created OS 03 (3) 'Unix' │ │ │ │ +B9EF0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9EF1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9EF2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9EF4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9EF6 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9EFA CRC C7E6B489 (3353785481) │ │ │ │ +B9EFE Compressed Size 00001AB3 (6835) │ │ │ │ +B9F02 Uncompressed Size 00005F00 (24320) │ │ │ │ +B9F06 Filename Length 0017 (23) │ │ │ │ +B9F08 Extra Length 0018 (24) │ │ │ │ +B9F0A Comment Length 0000 (0) │ │ │ │ +B9F0C Disk Start 0000 (0) │ │ │ │ +B9F0E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9F10 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9F14 Local Header Offset 00073911 (473361) │ │ │ │ +B9F18 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9F18: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9F2F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9F31 Length 0005 (5) │ │ │ │ +B9F33 Flags 01 (1) 'Modification' │ │ │ │ +B9F34 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9F38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9F3A Length 000B (11) │ │ │ │ +B9F3C Version 01 (1) │ │ │ │ +B9F3D UID Size 04 (4) │ │ │ │ +B9F3E UID 00000000 (0) │ │ │ │ +B9F42 GID Size 04 (4) │ │ │ │ +B9F43 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9F47 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +B9F4B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9F4C Created OS 03 (3) 'Unix' │ │ │ │ +B9F4D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9F4E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9F4F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9F51 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9F53 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9F57 CRC 11E32AF1 (300100337) │ │ │ │ +B9F5B Compressed Size 00000ED3 (3795) │ │ │ │ +B9F5F Uncompressed Size 000038E2 (14562) │ │ │ │ +B9F63 Filename Length 0023 (35) │ │ │ │ +B9F65 Extra Length 0018 (24) │ │ │ │ +B9F67 Comment Length 0000 (0) │ │ │ │ +B9F69 Disk Start 0000 (0) │ │ │ │ +B9F6B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9F6D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9F71 Local Header Offset 00075415 (480277) │ │ │ │ +B9F75 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9F75: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9F98 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9F9A Length 0005 (5) │ │ │ │ +B9F9C Flags 01 (1) 'Modification' │ │ │ │ +B9F9D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9FA1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9FA3 Length 000B (11) │ │ │ │ +B9FA5 Version 01 (1) │ │ │ │ +B9FA6 UID Size 04 (4) │ │ │ │ +B9FA7 UID 00000000 (0) │ │ │ │ +B9FAB GID Size 04 (4) │ │ │ │ +B9FAC GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9FB0 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +B9FB4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9FB5 Created OS 03 (3) 'Unix' │ │ │ │ +B9FB6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9FB7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9FB8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9FBA Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9FBC Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +B9FC0 CRC 2DB7929F (767005343) │ │ │ │ +B9FC4 Compressed Size 00000113 (275) │ │ │ │ +B9FC8 Uncompressed Size 000001F3 (499) │ │ │ │ +B9FCC Filename Length 001B (27) │ │ │ │ +B9FCE Extra Length 0018 (24) │ │ │ │ +B9FD0 Comment Length 0000 (0) │ │ │ │ +B9FD2 Disk Start 0000 (0) │ │ │ │ +B9FD4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9FD6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9FDA Local Header Offset 00076345 (484165) │ │ │ │ +B9FDE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9FDE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9FF9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9FFB Length 0005 (5) │ │ │ │ +B9FFD Flags 01 (1) 'Modification' │ │ │ │ +B9FFE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA002 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA004 Length 000B (11) │ │ │ │ +BA006 Version 01 (1) │ │ │ │ +BA007 UID Size 04 (4) │ │ │ │ +BA008 UID 00000000 (0) │ │ │ │ +BA00C GID Size 04 (4) │ │ │ │ +BA00D GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA011 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +BA015 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA016 Created OS 03 (3) 'Unix' │ │ │ │ +BA017 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA018 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA019 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA01B Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA01D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA021 CRC BA52E3CF (3125994447) │ │ │ │ +BA025 Compressed Size 00001892 (6290) │ │ │ │ +BA029 Uncompressed Size 00008FAA (36778) │ │ │ │ +BA02D Filename Length 001D (29) │ │ │ │ +BA02F Extra Length 0018 (24) │ │ │ │ +BA031 Comment Length 0000 (0) │ │ │ │ +BA033 Disk Start 0000 (0) │ │ │ │ +BA035 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA037 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA03B Local Header Offset 000764AD (484525) │ │ │ │ +BA03F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA03F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA05C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA05E Length 0005 (5) │ │ │ │ +BA060 Flags 01 (1) 'Modification' │ │ │ │ +BA061 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA065 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA067 Length 000B (11) │ │ │ │ +BA069 Version 01 (1) │ │ │ │ +BA06A UID Size 04 (4) │ │ │ │ +BA06B UID 00000000 (0) │ │ │ │ +BA06F GID Size 04 (4) │ │ │ │ +BA070 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA074 CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +BA078 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA079 Created OS 03 (3) 'Unix' │ │ │ │ +BA07A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA07B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA07C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA07E Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA080 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA084 CRC 227D9DE1 (578657761) │ │ │ │ +BA088 Compressed Size 0000164B (5707) │ │ │ │ +BA08C Uncompressed Size 00003A99 (15001) │ │ │ │ +BA090 Filename Length 0015 (21) │ │ │ │ +BA092 Extra Length 0018 (24) │ │ │ │ +BA094 Comment Length 0000 (0) │ │ │ │ +BA096 Disk Start 0000 (0) │ │ │ │ +BA098 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA09A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA09E Local Header Offset 00077D96 (490902) │ │ │ │ +BA0A2 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA0A2: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA0B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA0B9 Length 0005 (5) │ │ │ │ +BA0BB Flags 01 (1) 'Modification' │ │ │ │ +BA0BC Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA0C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA0C2 Length 000B (11) │ │ │ │ +BA0C4 Version 01 (1) │ │ │ │ +BA0C5 UID Size 04 (4) │ │ │ │ +BA0C6 UID 00000000 (0) │ │ │ │ +BA0CA GID Size 04 (4) │ │ │ │ +BA0CB GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA0CF CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +BA0D3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA0D4 Created OS 03 (3) 'Unix' │ │ │ │ +BA0D5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA0D6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA0D7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA0D9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA0DB Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA0DF CRC 51B591AB (1370853803) │ │ │ │ +BA0E3 Compressed Size 00003D36 (15670) │ │ │ │ +BA0E7 Uncompressed Size 0001219B (74139) │ │ │ │ +BA0EB Filename Length 0016 (22) │ │ │ │ +BA0ED Extra Length 0018 (24) │ │ │ │ +BA0EF Comment Length 0000 (0) │ │ │ │ +BA0F1 Disk Start 0000 (0) │ │ │ │ +BA0F3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA0F5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA0F9 Local Header Offset 00079430 (496688) │ │ │ │ +BA0FD Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA0FD: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA113 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA115 Length 0005 (5) │ │ │ │ +BA117 Flags 01 (1) 'Modification' │ │ │ │ +BA118 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA11C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA11E Length 000B (11) │ │ │ │ +BA120 Version 01 (1) │ │ │ │ +BA121 UID Size 04 (4) │ │ │ │ +BA122 UID 00000000 (0) │ │ │ │ +BA126 GID Size 04 (4) │ │ │ │ +BA127 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA12B CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +BA12F Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA130 Created OS 03 (3) 'Unix' │ │ │ │ +BA131 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA132 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA133 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA135 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA137 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA13B CRC F90938D5 (4178131157) │ │ │ │ +BA13F Compressed Size 00003E8A (16010) │ │ │ │ +BA143 Uncompressed Size 0001C179 (115065) │ │ │ │ +BA147 Filename Length 0019 (25) │ │ │ │ +BA149 Extra Length 0018 (24) │ │ │ │ +BA14B Comment Length 0000 (0) │ │ │ │ +BA14D Disk Start 0000 (0) │ │ │ │ +BA14F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA151 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA155 Local Header Offset 0007D1B6 (512438) │ │ │ │ +BA159 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA159: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA172 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA174 Length 0005 (5) │ │ │ │ +BA176 Flags 01 (1) 'Modification' │ │ │ │ +BA177 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA17B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA17D Length 000B (11) │ │ │ │ +BA17F Version 01 (1) │ │ │ │ +BA180 UID Size 04 (4) │ │ │ │ +BA181 UID 00000000 (0) │ │ │ │ +BA185 GID Size 04 (4) │ │ │ │ +BA186 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA18A CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +BA18E Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA18F Created OS 03 (3) 'Unix' │ │ │ │ +BA190 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA191 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA192 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA194 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA196 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA19A CRC B639B633 (3057235507) │ │ │ │ +BA19E Compressed Size 0000088E (2190) │ │ │ │ +BA1A2 Uncompressed Size 0000362C (13868) │ │ │ │ +BA1A6 Filename Length 0011 (17) │ │ │ │ +BA1A8 Extra Length 0018 (24) │ │ │ │ +BA1AA Comment Length 0000 (0) │ │ │ │ +BA1AC Disk Start 0000 (0) │ │ │ │ +BA1AE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA1B0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA1B4 Local Header Offset 00081093 (528531) │ │ │ │ +BA1B8 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA1B8: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA1C9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA1CB Length 0005 (5) │ │ │ │ +BA1CD Flags 01 (1) 'Modification' │ │ │ │ +BA1CE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA1D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA1D4 Length 000B (11) │ │ │ │ +BA1D6 Version 01 (1) │ │ │ │ +BA1D7 UID Size 04 (4) │ │ │ │ +BA1D8 UID 00000000 (0) │ │ │ │ +BA1DC GID Size 04 (4) │ │ │ │ +BA1DD GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA1E1 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +BA1E5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA1E6 Created OS 03 (3) 'Unix' │ │ │ │ +BA1E7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA1E8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA1E9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA1EB Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA1ED Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA1F1 CRC 37D9EC62 (937028706) │ │ │ │ +BA1F5 Compressed Size 000051AF (20911) │ │ │ │ +BA1F9 Uncompressed Size 0001FBDD (130013) │ │ │ │ +BA1FD Filename Length 0015 (21) │ │ │ │ +BA1FF Extra Length 0018 (24) │ │ │ │ +BA201 Comment Length 0000 (0) │ │ │ │ +BA203 Disk Start 0000 (0) │ │ │ │ +BA205 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA207 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA20B Local Header Offset 0008196C (530796) │ │ │ │ +BA20F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA20F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA224 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA226 Length 0005 (5) │ │ │ │ +BA228 Flags 01 (1) 'Modification' │ │ │ │ +BA229 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA22D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA22F Length 000B (11) │ │ │ │ +BA231 Version 01 (1) │ │ │ │ +BA232 UID Size 04 (4) │ │ │ │ +BA233 UID 00000000 (0) │ │ │ │ +BA237 GID Size 04 (4) │ │ │ │ +BA238 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA23C CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +BA240 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA241 Created OS 03 (3) 'Unix' │ │ │ │ +BA242 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA243 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA244 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA246 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA248 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA24C CRC 2E361F32 (775298866) │ │ │ │ +BA250 Compressed Size 00001AFB (6907) │ │ │ │ +BA254 Uncompressed Size 00008257 (33367) │ │ │ │ +BA258 Filename Length 0019 (25) │ │ │ │ +BA25A Extra Length 0018 (24) │ │ │ │ +BA25C Comment Length 0000 (0) │ │ │ │ +BA25E Disk Start 0000 (0) │ │ │ │ +BA260 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA262 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA266 Local Header Offset 00086B6A (551786) │ │ │ │ +BA26A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA26A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA283 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA285 Length 0005 (5) │ │ │ │ +BA287 Flags 01 (1) 'Modification' │ │ │ │ +BA288 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA28C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA28E Length 000B (11) │ │ │ │ +BA290 Version 01 (1) │ │ │ │ +BA291 UID Size 04 (4) │ │ │ │ +BA292 UID 00000000 (0) │ │ │ │ +BA296 GID Size 04 (4) │ │ │ │ +BA297 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA29B CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +BA29F Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA2A0 Created OS 03 (3) 'Unix' │ │ │ │ +BA2A1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA2A2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA2A3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA2A5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA2A7 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA2AB CRC 49C26BB3 (1237478323) │ │ │ │ +BA2AF Compressed Size 00000D95 (3477) │ │ │ │ +BA2B3 Uncompressed Size 00002E9D (11933) │ │ │ │ +BA2B7 Filename Length 0018 (24) │ │ │ │ +BA2B9 Extra Length 0018 (24) │ │ │ │ +BA2BB Comment Length 0000 (0) │ │ │ │ +BA2BD Disk Start 0000 (0) │ │ │ │ +BA2BF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA2C1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA2C5 Local Header Offset 000886B8 (558776) │ │ │ │ +BA2C9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA2C9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA2E1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA2E3 Length 0005 (5) │ │ │ │ +BA2E5 Flags 01 (1) 'Modification' │ │ │ │ +BA2E6 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA2EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA2EC Length 000B (11) │ │ │ │ +BA2EE Version 01 (1) │ │ │ │ +BA2EF UID Size 04 (4) │ │ │ │ +BA2F0 UID 00000000 (0) │ │ │ │ +BA2F4 GID Size 04 (4) │ │ │ │ +BA2F5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA2F9 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +BA2FD Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA2FE Created OS 03 (3) 'Unix' │ │ │ │ +BA2FF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA300 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA301 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA303 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA305 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA309 CRC 42771396 (1115100054) │ │ │ │ +BA30D Compressed Size 000001DF (479) │ │ │ │ +BA311 Uncompressed Size 00000321 (801) │ │ │ │ +BA315 Filename Length 0011 (17) │ │ │ │ +BA317 Extra Length 0018 (24) │ │ │ │ +BA319 Comment Length 0000 (0) │ │ │ │ +BA31B Disk Start 0000 (0) │ │ │ │ +BA31D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA31F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA323 Local Header Offset 0008949F (562335) │ │ │ │ +BA327 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA327: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA338 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA33A Length 0005 (5) │ │ │ │ +BA33C Flags 01 (1) 'Modification' │ │ │ │ +BA33D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA341 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA343 Length 000B (11) │ │ │ │ +BA345 Version 01 (1) │ │ │ │ +BA346 UID Size 04 (4) │ │ │ │ +BA347 UID 00000000 (0) │ │ │ │ +BA34B GID Size 04 (4) │ │ │ │ +BA34C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA350 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +BA354 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA355 Created OS 03 (3) 'Unix' │ │ │ │ +BA356 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA357 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA358 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA35A Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA35C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA360 CRC DB0CDFF8 (3675054072) │ │ │ │ +BA364 Compressed Size 000006BC (1724) │ │ │ │ +BA368 Uncompressed Size 0000141C (5148) │ │ │ │ +BA36C Filename Length 0019 (25) │ │ │ │ +BA36E Extra Length 0018 (24) │ │ │ │ +BA370 Comment Length 0000 (0) │ │ │ │ +BA372 Disk Start 0000 (0) │ │ │ │ +BA374 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA376 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA37A Local Header Offset 000896C9 (562889) │ │ │ │ +BA37E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA37E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA397 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA399 Length 0005 (5) │ │ │ │ +BA39B Flags 01 (1) 'Modification' │ │ │ │ +BA39C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA3A0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA3A2 Length 000B (11) │ │ │ │ +BA3A4 Version 01 (1) │ │ │ │ +BA3A5 UID Size 04 (4) │ │ │ │ +BA3A6 UID 00000000 (0) │ │ │ │ +BA3AA GID Size 04 (4) │ │ │ │ +BA3AB GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA3AF CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +BA3B3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA3B4 Created OS 03 (3) 'Unix' │ │ │ │ +BA3B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA3B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA3B7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA3B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA3BB Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA3BF CRC 65917CFB (1704033531) │ │ │ │ +BA3C3 Compressed Size 00001B89 (7049) │ │ │ │ +BA3C7 Uncompressed Size 00009F5D (40797) │ │ │ │ +BA3CB Filename Length 0018 (24) │ │ │ │ +BA3CD Extra Length 0018 (24) │ │ │ │ +BA3CF Comment Length 0000 (0) │ │ │ │ +BA3D1 Disk Start 0000 (0) │ │ │ │ +BA3D3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA3D5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA3D9 Local Header Offset 00089DD8 (564696) │ │ │ │ +BA3DD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA3DD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA3F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA3F7 Length 0005 (5) │ │ │ │ +BA3F9 Flags 01 (1) 'Modification' │ │ │ │ +BA3FA Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA3FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA400 Length 000B (11) │ │ │ │ +BA402 Version 01 (1) │ │ │ │ +BA403 UID Size 04 (4) │ │ │ │ +BA404 UID 00000000 (0) │ │ │ │ +BA408 GID Size 04 (4) │ │ │ │ +BA409 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA40D CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +BA411 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA412 Created OS 03 (3) 'Unix' │ │ │ │ +BA413 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA414 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA415 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA417 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA419 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA41D CRC FCA2FA5B (4238539355) │ │ │ │ +BA421 Compressed Size 000016FA (5882) │ │ │ │ +BA425 Uncompressed Size 00008B10 (35600) │ │ │ │ +BA429 Filename Length 0012 (18) │ │ │ │ +BA42B Extra Length 0018 (24) │ │ │ │ +BA42D Comment Length 0000 (0) │ │ │ │ +BA42F Disk Start 0000 (0) │ │ │ │ +BA431 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA433 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA437 Local Header Offset 0008B9B3 (571827) │ │ │ │ +BA43B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA43B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA44D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA44F Length 0005 (5) │ │ │ │ +BA451 Flags 01 (1) 'Modification' │ │ │ │ +BA452 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA456 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA458 Length 000B (11) │ │ │ │ +BA45A Version 01 (1) │ │ │ │ +BA45B UID Size 04 (4) │ │ │ │ +BA45C UID 00000000 (0) │ │ │ │ +BA460 GID Size 04 (4) │ │ │ │ +BA461 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA465 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +BA469 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA46A Created OS 03 (3) 'Unix' │ │ │ │ +BA46B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA46C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA46D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA46F Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA471 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA475 CRC 24445419 (608457753) │ │ │ │ +BA479 Compressed Size 00001E10 (7696) │ │ │ │ +BA47D Uncompressed Size 00008801 (34817) │ │ │ │ +BA481 Filename Length 0016 (22) │ │ │ │ +BA483 Extra Length 0018 (24) │ │ │ │ +BA485 Comment Length 0000 (0) │ │ │ │ +BA487 Disk Start 0000 (0) │ │ │ │ +BA489 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA48B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA48F Local Header Offset 0008D0F9 (577785) │ │ │ │ +BA493 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA493: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA4A9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA4AB Length 0005 (5) │ │ │ │ +BA4AD Flags 01 (1) 'Modification' │ │ │ │ +BA4AE Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA4B2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA4B4 Length 000B (11) │ │ │ │ +BA4B6 Version 01 (1) │ │ │ │ +BA4B7 UID Size 04 (4) │ │ │ │ +BA4B8 UID 00000000 (0) │ │ │ │ +BA4BC GID Size 04 (4) │ │ │ │ +BA4BD GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA4C1 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +BA4C5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA4C6 Created OS 03 (3) 'Unix' │ │ │ │ +BA4C7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA4C8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA4C9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA4CB Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA4CD Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA4D1 CRC A41B923B (2753270331) │ │ │ │ +BA4D5 Compressed Size 000029AB (10667) │ │ │ │ +BA4D9 Uncompressed Size 0000D04D (53325) │ │ │ │ +BA4DD Filename Length 001A (26) │ │ │ │ +BA4DF Extra Length 0018 (24) │ │ │ │ +BA4E1 Comment Length 0000 (0) │ │ │ │ +BA4E3 Disk Start 0000 (0) │ │ │ │ +BA4E5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA4E7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA4EB Local Header Offset 0008EF59 (585561) │ │ │ │ +BA4EF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA4EF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA509 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA50B Length 0005 (5) │ │ │ │ +BA50D Flags 01 (1) 'Modification' │ │ │ │ +BA50E Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA512 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA514 Length 000B (11) │ │ │ │ +BA516 Version 01 (1) │ │ │ │ +BA517 UID Size 04 (4) │ │ │ │ +BA518 UID 00000000 (0) │ │ │ │ +BA51C GID Size 04 (4) │ │ │ │ +BA51D GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA521 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +BA525 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA526 Created OS 03 (3) 'Unix' │ │ │ │ +BA527 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA528 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA529 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA52B Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA52D Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA531 CRC 25687D13 (627604755) │ │ │ │ +BA535 Compressed Size 000009AA (2474) │ │ │ │ +BA539 Uncompressed Size 00001DB4 (7604) │ │ │ │ +BA53D Filename Length 0018 (24) │ │ │ │ +BA53F Extra Length 0018 (24) │ │ │ │ +BA541 Comment Length 0000 (0) │ │ │ │ +BA543 Disk Start 0000 (0) │ │ │ │ +BA545 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA547 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA54B Local Header Offset 00091958 (596312) │ │ │ │ +BA54F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA54F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA567 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA569 Length 0005 (5) │ │ │ │ +BA56B Flags 01 (1) 'Modification' │ │ │ │ +BA56C Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA570 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA572 Length 000B (11) │ │ │ │ +BA574 Version 01 (1) │ │ │ │ +BA575 UID Size 04 (4) │ │ │ │ +BA576 UID 00000000 (0) │ │ │ │ +BA57A GID Size 04 (4) │ │ │ │ +BA57B GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA57F CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +BA583 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA584 Created OS 03 (3) 'Unix' │ │ │ │ +BA585 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA586 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA587 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA589 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA58B Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA58F CRC F0556E9A (4032130714) │ │ │ │ +BA593 Compressed Size 000152EE (86766) │ │ │ │ +BA597 Uncompressed Size 000159F8 (88568) │ │ │ │ +BA59B Filename Length 001E (30) │ │ │ │ +BA59D Extra Length 0018 (24) │ │ │ │ +BA59F Comment Length 0000 (0) │ │ │ │ +BA5A1 Disk Start 0000 (0) │ │ │ │ +BA5A3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA5A5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA5A9 Local Header Offset 00092354 (598868) │ │ │ │ +BA5AD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA5AD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA5CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA5CD Length 0005 (5) │ │ │ │ +BA5CF Flags 01 (1) 'Modification' │ │ │ │ +BA5D0 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA5D4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA5D6 Length 000B (11) │ │ │ │ +BA5D8 Version 01 (1) │ │ │ │ +BA5D9 UID Size 04 (4) │ │ │ │ +BA5DA UID 00000000 (0) │ │ │ │ +BA5DE GID Size 04 (4) │ │ │ │ +BA5DF GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA5E3 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +BA5E7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA5E8 Created OS 03 (3) 'Unix' │ │ │ │ +BA5E9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA5EA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA5EB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA5ED Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA5EF Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA5F3 CRC F5E2129F (4125233823) │ │ │ │ +BA5F7 Compressed Size 000016BC (5820) │ │ │ │ +BA5FB Uncompressed Size 000016CD (5837) │ │ │ │ +BA5FF Filename Length 0015 (21) │ │ │ │ +BA601 Extra Length 0018 (24) │ │ │ │ +BA603 Comment Length 0000 (0) │ │ │ │ +BA605 Disk Start 0000 (0) │ │ │ │ +BA607 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA609 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA60D Local Header Offset 000A769A (685722) │ │ │ │ +BA611 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA611: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA626 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA628 Length 0005 (5) │ │ │ │ +BA62A Flags 01 (1) 'Modification' │ │ │ │ +BA62B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA62F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA631 Length 000B (11) │ │ │ │ +BA633 Version 01 (1) │ │ │ │ +BA634 UID Size 04 (4) │ │ │ │ +BA635 UID 00000000 (0) │ │ │ │ +BA639 GID Size 04 (4) │ │ │ │ +BA63A GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA63E CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +BA642 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA643 Created OS 03 (3) 'Unix' │ │ │ │ +BA644 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA645 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA646 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA648 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA64A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA64E CRC F5E2129F (4125233823) │ │ │ │ +BA652 Compressed Size 000016BC (5820) │ │ │ │ +BA656 Uncompressed Size 000016CD (5837) │ │ │ │ +BA65A Filename Length 001C (28) │ │ │ │ +BA65C Extra Length 0018 (24) │ │ │ │ +BA65E Comment Length 0000 (0) │ │ │ │ +BA660 Disk Start 0000 (0) │ │ │ │ +BA662 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA664 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA668 Local Header Offset 000A8DA5 (691621) │ │ │ │ +BA66C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA66C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA688 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA68A Length 0005 (5) │ │ │ │ +BA68C Flags 01 (1) 'Modification' │ │ │ │ +BA68D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA691 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA693 Length 000B (11) │ │ │ │ +BA695 Version 01 (1) │ │ │ │ +BA696 UID Size 04 (4) │ │ │ │ +BA697 UID 00000000 (0) │ │ │ │ +BA69B GID Size 04 (4) │ │ │ │ +BA69C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA6A0 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +BA6A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA6A5 Created OS 03 (3) 'Unix' │ │ │ │ +BA6A6 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA6A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA6A8 General Purpose Flag 0000 (0) │ │ │ │ +BA6AA Compression Method 0000 (0) 'Stored' │ │ │ │ +BA6AC Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA6B0 CRC FC95F24B (4237685323) │ │ │ │ +BA6B4 Compressed Size 00001B84 (7044) │ │ │ │ +BA6B8 Uncompressed Size 00001B84 (7044) │ │ │ │ +BA6BC Filename Length 0016 (22) │ │ │ │ +BA6BE Extra Length 0018 (24) │ │ │ │ +BA6C0 Comment Length 0000 (0) │ │ │ │ +BA6C2 Disk Start 0000 (0) │ │ │ │ +BA6C4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA6C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA6CA Local Header Offset 000AA4B7 (697527) │ │ │ │ +BA6CE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA6CE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA6E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA6E6 Length 0005 (5) │ │ │ │ +BA6E8 Flags 01 (1) 'Modification' │ │ │ │ +BA6E9 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA6ED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA6EF Length 000B (11) │ │ │ │ +BA6F1 Version 01 (1) │ │ │ │ +BA6F2 UID Size 04 (4) │ │ │ │ +BA6F3 UID 00000000 (0) │ │ │ │ +BA6F7 GID Size 04 (4) │ │ │ │ +BA6F8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA6FC CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +BA700 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA701 Created OS 03 (3) 'Unix' │ │ │ │ +BA702 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA703 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA704 General Purpose Flag 0000 (0) │ │ │ │ +BA706 Compression Method 0000 (0) 'Stored' │ │ │ │ +BA708 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA70C CRC D0D71F86 (3503759238) │ │ │ │ +BA710 Compressed Size 00000B7B (2939) │ │ │ │ +BA714 Uncompressed Size 00000B7B (2939) │ │ │ │ +BA718 Filename Length 0016 (22) │ │ │ │ +BA71A Extra Length 0018 (24) │ │ │ │ +BA71C Comment Length 0000 (0) │ │ │ │ +BA71E Disk Start 0000 (0) │ │ │ │ +BA720 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA722 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA726 Local Header Offset 000AC08B (704651) │ │ │ │ +BA72A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA72A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA740 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA742 Length 0005 (5) │ │ │ │ +BA744 Flags 01 (1) 'Modification' │ │ │ │ +BA745 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA749 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA74B Length 000B (11) │ │ │ │ +BA74D Version 01 (1) │ │ │ │ +BA74E UID Size 04 (4) │ │ │ │ +BA74F UID 00000000 (0) │ │ │ │ +BA753 GID Size 04 (4) │ │ │ │ +BA754 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA758 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +BA75C Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA75D Created OS 03 (3) 'Unix' │ │ │ │ +BA75E Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA75F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA760 General Purpose Flag 0000 (0) │ │ │ │ +BA762 Compression Method 0000 (0) 'Stored' │ │ │ │ +BA764 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA768 CRC FFF9C4D2 (4294558930) │ │ │ │ +BA76C Compressed Size 0000138F (5007) │ │ │ │ +BA770 Uncompressed Size 0000138F (5007) │ │ │ │ +BA774 Filename Length 0016 (22) │ │ │ │ +BA776 Extra Length 0018 (24) │ │ │ │ +BA778 Comment Length 0000 (0) │ │ │ │ +BA77A Disk Start 0000 (0) │ │ │ │ +BA77C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA77E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA782 Local Header Offset 000ACC56 (707670) │ │ │ │ +BA786 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA786: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA79C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA79E Length 0005 (5) │ │ │ │ +BA7A0 Flags 01 (1) 'Modification' │ │ │ │ +BA7A1 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA7A5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA7A7 Length 000B (11) │ │ │ │ +BA7A9 Version 01 (1) │ │ │ │ +BA7AA UID Size 04 (4) │ │ │ │ +BA7AB UID 00000000 (0) │ │ │ │ +BA7AF GID Size 04 (4) │ │ │ │ +BA7B0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA7B4 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +BA7B8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA7B9 Created OS 03 (3) 'Unix' │ │ │ │ +BA7BA Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA7BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA7BC General Purpose Flag 0000 (0) │ │ │ │ +BA7BE Compression Method 0000 (0) 'Stored' │ │ │ │ +BA7C0 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA7C4 CRC A1037E8E (2701360782) │ │ │ │ +BA7C8 Compressed Size 0000145E (5214) │ │ │ │ +BA7CC Uncompressed Size 0000145E (5214) │ │ │ │ +BA7D0 Filename Length 0016 (22) │ │ │ │ +BA7D2 Extra Length 0018 (24) │ │ │ │ +BA7D4 Comment Length 0000 (0) │ │ │ │ +BA7D6 Disk Start 0000 (0) │ │ │ │ +BA7D8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA7DA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA7DE Local Header Offset 000AE035 (712757) │ │ │ │ +BA7E2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA7E2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA7F8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA7FA Length 0005 (5) │ │ │ │ +BA7FC Flags 01 (1) 'Modification' │ │ │ │ +BA7FD Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA801 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA803 Length 000B (11) │ │ │ │ +BA805 Version 01 (1) │ │ │ │ +BA806 UID Size 04 (4) │ │ │ │ +BA807 UID 00000000 (0) │ │ │ │ +BA80B GID Size 04 (4) │ │ │ │ +BA80C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA810 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +BA814 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA815 Created OS 03 (3) 'Unix' │ │ │ │ +BA816 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA817 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA818 General Purpose Flag 0000 (0) │ │ │ │ +BA81A Compression Method 0000 (0) 'Stored' │ │ │ │ +BA81C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA820 CRC 5E9E64F1 (1587438833) │ │ │ │ +BA824 Compressed Size 000008EC (2284) │ │ │ │ +BA828 Uncompressed Size 000008EC (2284) │ │ │ │ +BA82C Filename Length 0016 (22) │ │ │ │ +BA82E Extra Length 0018 (24) │ │ │ │ +BA830 Comment Length 0000 (0) │ │ │ │ +BA832 Disk Start 0000 (0) │ │ │ │ +BA834 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA836 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA83A Local Header Offset 000AF4E3 (718051) │ │ │ │ +BA83E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA83E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA854 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA856 Length 0005 (5) │ │ │ │ +BA858 Flags 01 (1) 'Modification' │ │ │ │ +BA859 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA85D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA85F Length 000B (11) │ │ │ │ +BA861 Version 01 (1) │ │ │ │ +BA862 UID Size 04 (4) │ │ │ │ +BA863 UID 00000000 (0) │ │ │ │ +BA867 GID Size 04 (4) │ │ │ │ +BA868 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA86C CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ +BA870 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA871 Created OS 03 (3) 'Unix' │ │ │ │ +BA872 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BA873 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA874 General Purpose Flag 0000 (0) │ │ │ │ +BA876 Compression Method 0000 (0) 'Stored' │ │ │ │ +BA878 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA87C CRC 42E340AB (1122189483) │ │ │ │ +BA880 Compressed Size 00001F2E (7982) │ │ │ │ +BA884 Uncompressed Size 00001F2E (7982) │ │ │ │ +BA888 Filename Length 001E (30) │ │ │ │ +BA88A Extra Length 0018 (24) │ │ │ │ +BA88C Comment Length 0000 (0) │ │ │ │ +BA88E Disk Start 0000 (0) │ │ │ │ +BA890 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA892 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA896 Local Header Offset 000AFE1F (720415) │ │ │ │ +BA89A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA89A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA8B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA8BA Length 0005 (5) │ │ │ │ +BA8BC Flags 01 (1) 'Modification' │ │ │ │ +BA8BD Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA8C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA8C3 Length 000B (11) │ │ │ │ +BA8C5 Version 01 (1) │ │ │ │ +BA8C6 UID Size 04 (4) │ │ │ │ +BA8C7 UID 00000000 (0) │ │ │ │ +BA8CB GID Size 04 (4) │ │ │ │ +BA8CC GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA8D0 CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ +BA8D4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA8D5 Created OS 03 (3) 'Unix' │ │ │ │ +BA8D6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA8D7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA8D8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA8DA Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA8DC Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA8E0 CRC CDFF1E8F (3456048783) │ │ │ │ +BA8E4 Compressed Size 00003D61 (15713) │ │ │ │ +BA8E8 Uncompressed Size 0001664D (91725) │ │ │ │ +BA8EC Filename Length 001A (26) │ │ │ │ +BA8EE Extra Length 0018 (24) │ │ │ │ +BA8F0 Comment Length 0000 (0) │ │ │ │ +BA8F2 Disk Start 0000 (0) │ │ │ │ +BA8F4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA8F6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA8FA Local Header Offset 000B1DA5 (728485) │ │ │ │ +BA8FE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA8FE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA918 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA91A Length 0005 (5) │ │ │ │ +BA91C Flags 01 (1) 'Modification' │ │ │ │ +BA91D Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA921 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA923 Length 000B (11) │ │ │ │ +BA925 Version 01 (1) │ │ │ │ +BA926 UID Size 04 (4) │ │ │ │ +BA927 UID 00000000 (0) │ │ │ │ +BA92B GID Size 04 (4) │ │ │ │ +BA92C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA930 CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ +BA934 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA935 Created OS 03 (3) 'Unix' │ │ │ │ +BA936 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA937 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA938 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA93A Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA93C Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA940 CRC F3D8ADF1 (4091063793) │ │ │ │ +BA944 Compressed Size 000029CE (10702) │ │ │ │ +BA948 Uncompressed Size 0000BB37 (47927) │ │ │ │ +BA94C Filename Length 0018 (24) │ │ │ │ +BA94E Extra Length 0018 (24) │ │ │ │ +BA950 Comment Length 0000 (0) │ │ │ │ +BA952 Disk Start 0000 (0) │ │ │ │ +BA954 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA956 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA95A Local Header Offset 000B5B5A (744282) │ │ │ │ +BA95E Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA95E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA976 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA978 Length 0005 (5) │ │ │ │ +BA97A Flags 01 (1) 'Modification' │ │ │ │ +BA97B Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA97F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA981 Length 000B (11) │ │ │ │ +BA983 Version 01 (1) │ │ │ │ +BA984 UID Size 04 (4) │ │ │ │ +BA985 UID 00000000 (0) │ │ │ │ +BA989 GID Size 04 (4) │ │ │ │ +BA98A GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA98E CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ +BA992 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA993 Created OS 03 (3) 'Unix' │ │ │ │ +BA994 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA995 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA996 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA998 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA99A Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA99E CRC DCB3B516 (3702764822) │ │ │ │ +BA9A2 Compressed Size 000000AE (174) │ │ │ │ +BA9A6 Uncompressed Size 000000FC (252) │ │ │ │ +BA9AA Filename Length 0016 (22) │ │ │ │ +BA9AC Extra Length 0018 (24) │ │ │ │ +BA9AE Comment Length 0000 (0) │ │ │ │ +BA9B0 Disk Start 0000 (0) │ │ │ │ +BA9B2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA9B4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA9B8 Local Header Offset 000B857A (755066) │ │ │ │ +BA9BC Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA9BC: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA9D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA9D4 Length 0005 (5) │ │ │ │ +BA9D6 Flags 01 (1) 'Modification' │ │ │ │ +BA9D7 Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA9DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA9DD Length 000B (11) │ │ │ │ +BA9DF Version 01 (1) │ │ │ │ +BA9E0 UID Size 04 (4) │ │ │ │ +BA9E1 UID 00000000 (0) │ │ │ │ +BA9E5 GID Size 04 (4) │ │ │ │ +BA9E6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA9EA CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ +BA9EE Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA9EF Created OS 03 (3) 'Unix' │ │ │ │ +BA9F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA9F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA9F2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA9F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA9F6 Modification Time 5C623F61 (1549942625) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BA9FA CRC 58439733 (1480824627) │ │ │ │ +BA9FE Compressed Size 00000077 (119) │ │ │ │ +BAA02 Uncompressed Size 000000A2 (162) │ │ │ │ +BAA06 Filename Length 002D (45) │ │ │ │ +BAA08 Extra Length 0018 (24) │ │ │ │ +BAA0A Comment Length 0000 (0) │ │ │ │ +BAA0C Disk Start 0000 (0) │ │ │ │ +BAA0E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAA10 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAA14 Local Header Offset 000B8678 (755320) │ │ │ │ +BAA18 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAA18: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAA45 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAA47 Length 0005 (5) │ │ │ │ +BAA49 Flags 01 (1) 'Modification' │ │ │ │ +BAA4A Modification Time 69A54346 (1772438342) 'Mon Mar 2 07:59:02 2026' │ │ │ │ +BAA4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAA50 Length 000B (11) │ │ │ │ +BAA52 Version 01 (1) │ │ │ │ +BAA53 UID Size 04 (4) │ │ │ │ +BAA54 UID 00000000 (0) │ │ │ │ +BAA58 GID Size 04 (4) │ │ │ │ +BAA59 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAA5D END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +BAA61 Number of this disk 0000 (0) │ │ │ │ +BAA63 Central Dir Disk no 0000 (0) │ │ │ │ +BAA65 Entries in this disk 0060 (96) │ │ │ │ +BAA67 Total Entries 0060 (96) │ │ │ │ +BAA69 Size of Central Dir 00002307 (8967) │ │ │ │ +BAA6D Offset to Central Dir 000B8756 (755542) │ │ │ │ +BAA71 Comment Length 0000 (0) │ │ │ │ # │ │ │ │ # Warning Count: 192 │ │ │ │ # │ │ │ │ # Done │ │ │ ├── filetype from file(1) │ │ │ │ @@ -1 +1 @@ │ │ │ │ -Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Jan 15 2026 13:53:38, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Mar 02 2026 07:59:02, uncompressed size 20, method=store │ │ │ ├── OEBPS/vulnerabilities.xhtml │ │ │ │ @@ -48,69 +48,69 @@ │ │ │ │ vulnerability (affected), the affected Erlang/OTP releases, namely 28.0, │ │ │ │ 28.0.1, and 28.0.2, and the Erlang/OTP application that was vulnerable │ │ │ │ in application version ssh@5.3, ssh@5.3.1, and ssh@5.3.2. │ │ │ │ Erlang/OTP reports the affected versions using the release and the │ │ │ │ application versions because it is possible to update the application independently │ │ │ │ from the release. │ │ │ │ In some cases, there may be an optional action statement that describes a workaround │ │ │ │ -to avoid the mentioned vulnerability.

{
│ │ │ │ -  "vulnerability": {
│ │ │ │ +to avoid the mentioned vulnerability.

{
│ │ │ │ +  "vulnerability": {
│ │ │ │      "name": "CVE-2025-48038"
│ │ │ │ -  },
│ │ │ │ +  },
│ │ │ │    "timestamp": "2025-09-16T08:22:13.223967395Z",
│ │ │ │ -  "products": [
│ │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
│ │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
│ │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
│ │ │ │ -    { "@id": "pkg:otp/ssh@5.3" },
│ │ │ │ -    { "@id": "pkg:otp/ssh@5.3.1" },
│ │ │ │ -    { "@id": "pkg:otp/ssh@5.3.2" }
│ │ │ │ -  ],
│ │ │ │ +  "products": [
│ │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
│ │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
│ │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
│ │ │ │ +    { "@id": "pkg:otp/ssh@5.3" },
│ │ │ │ +    { "@id": "pkg:otp/ssh@5.3.1" },
│ │ │ │ +    { "@id": "pkg:otp/ssh@5.3.2" }
│ │ │ │ +  ],
│ │ │ │    "status": "affected",
│ │ │ │    "action_statement": "Update to any of the following versions: pkg:otp/ssh@5.3.3",
│ │ │ │    "action_statement_timestamp": "2025-09-16T08:22:13.223967395Z"
│ │ │ │ -},

Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ │ +},

Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ │ As an example, there is a new statement for CVE-2025-48038 with status fixed, │ │ │ │ that links to the first release that do not suffer from CVE-2025-48038, namely │ │ │ │ -OTP version 28.0.3 and application ssh@5.3.3.

{
│ │ │ │ -  "vulnerability": {
│ │ │ │ +OTP version 28.0.3 and application ssh@5.3.3. 

{
│ │ │ │ +  "vulnerability": {
│ │ │ │      "name": "CVE-2025-48038"
│ │ │ │ -  },
│ │ │ │ +  },
│ │ │ │    "timestamp": "2025-09-16T08:22:13.241103494Z",
│ │ │ │ -  "products": [
│ │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
│ │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
│ │ │ │ -    { "@id": "pkg:otp/ssh@5.3.3" }
│ │ │ │ -  ],
│ │ │ │ +  "products": [
│ │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
│ │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
│ │ │ │ +    { "@id": "pkg:otp/ssh@5.3.3" }
│ │ │ │ +  ],
│ │ │ │    "status": "fixed"
│ │ │ │ -},

│ │ │ │ +},

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Third Party VEX Statements │ │ │ │

│ │ │ │

Erlang/OTP generates statements for third parties from which the project depends │ │ │ │ on. It is really important to understand the scope of the third party │ │ │ │ applications, since Erlang/OTP vendors some libraries as part of the runtime.

Vendoring means that Erlang/OTP code contains a local copy of a library. │ │ │ │ There are numerous use cases for why this is necessary, and we will not cover the use cases here.

This excludes dynamically or statically linked libraries during the Erlang/OTP build process. For instance, any security related Erlang application will rely on dynamically or statically linked version of OpenSSL cryptolib.

Erlang/OTP reports vulnerabilities for any source code that is vulnerable and │ │ │ │ included in the Erlang/OTP release.

The OpenVEX statements for our third party libraries specify the affected/fixed │ │ │ │ version using the commit SHA1 from their respective repository. This is simply │ │ │ │ because our third party dependencies are in C/C++ and vulnerability scanners │ │ │ │ such as OSV report vulnerabilities in SHA1 ranges.

As an example, we mention that the OpenSSL code that Erlang/OTP vendors │ │ │ │ -is not susceptible for CVE-2023-6129, as follows:

{
│ │ │ │ -  "vulnerability": {
│ │ │ │ +is not susceptible for CVE-2023-6129, as follows:

{
│ │ │ │ +  "vulnerability": {
│ │ │ │      "name": "CVE-2023-6129"
│ │ │ │ -  },
│ │ │ │ +  },
│ │ │ │    "timestamp": "2025-06-18T12:18:16.47247833+02:00",
│ │ │ │ -  "products": [
│ │ │ │ -     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
│ │ │ │ -  ],
│ │ │ │ +  "products": [
│ │ │ │ +     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
│ │ │ │ +  ],
│ │ │ │    "status": "not_affected",
│ │ │ │    "justification": "vulnerable_code_not_present"
│ │ │ │ -}

Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ │ +}

Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ │ included with Erlang/OTP). If you build Erlang/OTP and link to any OpenSSL version (e.g., 3.5.2 or even 3.1.4) during the building process, │ │ │ │ your project has now a new build and runtime dependency and may be subject to CVE-2023-6129.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Windows Binaries │ │ │ │

│ │ │ ├── OEBPS/typespec.xhtml │ │ │ │ @@ -143,126 +143,126 @@ │ │ │ │ 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, -opaque, and │ │ │ │ --nominal attributes as in the following example:

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

The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ │ +-nominal attributes as in the following example:

-type my_struct_type() :: Type.
│ │ │ │ +-opaque my_opaq_type() :: Type.
│ │ │ │ +-nominal my_nominal_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.

Change

Nominal types were introduced in Erlang/OTP 28.

Types declared as nominal are type-checked according to the user-defined │ │ │ │ names instead of their structure. That is, -nominal feet() :: integer() and │ │ │ │ -nominal meter() :: integer() are not the same type, while if -type is │ │ │ │ used it would be.

Read more on Opaques and Nominals

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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).
      │ │ │ │ -ok
      │ │ │ │ -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 => []}}}]}]]}

      │ │ │ │ +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). │ │ │ │ +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 => []}}}]}]]}

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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).
      │ │ │ │ -ok
      │ │ │ │ -3> sys:trace(ch4, true).
      │ │ │ │ -ok
      │ │ │ │ -4> ch4:alloc().
      │ │ │ │ -ch4 event = {in,alloc,<0.88.0>}
      │ │ │ │ -ch4 event = {out,{ch4,1},<0.88.0>}
      │ │ │ │ +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).
      │ │ │ │ +ok
      │ │ │ │ +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).
      │ │ │ │ -ok
      │ │ │ │ -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|...]}]}

      │ │ │ │ +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). │ │ │ │ +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|...]}]}

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ │ @@ -29,24 +29,24 @@ │ │ │ │

        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.

        Change

        Since Erlang/OTP 28, Dialyzer checks opaques in their defining module in the │ │ │ │ same way as nominals. Outside of the defining module, Dialyzer checks │ │ │ │ opaques for opacity violations.

        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 . One exception │ │ │ │ is that =:= and =/= can be used between two opaques with the same name, or │ │ │ │ between an opaque and any(), as those comparisons do not reveal underlying │ │ │ │ types.
        • Use functions provided by the module for working with the type. For │ │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ ├── OEBPS/nominals.xhtml │ │ │ │ @@ -28,55 +28,55 @@ │ │ │ │ │ │ │ │

          For user-defined types │ │ │ │ defined with -type, the Erlang compiler will ignore their type names. This │ │ │ │ means the Erlang compiler uses a structural type system. Two types are seen as │ │ │ │ equivalent if their structures are the same. Type comparison is based on the │ │ │ │ structures of the types, not on how the user explicitly defines them. In the │ │ │ │ following example, meter() and foot() are equivalent, and neither differs │ │ │ │ -from the basic type integer().

          -type meter() :: integer().
          │ │ │ │ --type foot() :: integer().

          Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ │ +from the basic type integer().

          -type meter() :: integer().
          │ │ │ │ +-type foot() :: integer().

          Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ │ if and only if they are declared with the same type name. The syntax for │ │ │ │ declaring nominal types is -nominal.

          If meter() and foot() are defined as nominal types, they will no longer be │ │ │ │ compatible. When a function expects type meter(), passing in type foot() │ │ │ │ -will result in a warning raised by the type checker.

          -nominal meter() :: integer().
          │ │ │ │ --nominal foot() :: integer().

          The main use case of nominal types is to prevent accidental misuse of types with │ │ │ │ +will result in a warning raised by the type checker.

          -nominal meter() :: integer().
          │ │ │ │ +-nominal foot() :: integer().

          The main use case of nominal types is to prevent accidental misuse of types with │ │ │ │ the same structure. Within OTP, nominal type-checking is done in Dialyzer. The │ │ │ │ Erlang compiler does not perform nominal type-checking.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nominal Type-Checking Rules │ │ │ │

          │ │ │ │

          In general, if two nominal types have different names, and one is not derived │ │ │ │ from the other, they are not compatible. Dialyzer's nominal type-checking │ │ │ │ -aligns with the examples' expected results in this section.

          If we continue from the example above:

          -spec int_to_meter(integer()) -> meter().
          │ │ │ │ -int_to_meter(X) -> X.
          │ │ │ │ +aligns with the examples' expected results in this section.

          If we continue from the example above:

          -spec int_to_meter(integer()) -> meter().
          │ │ │ │ +int_to_meter(X) -> X.
          │ │ │ │  
          │ │ │ │ --spec foo() -> foot().
          │ │ │ │ -foo() -> int_to_meter(24).

          A type checker that performs nominal type-checking should raise a warning. │ │ │ │ +-spec foo() -> foot(). │ │ │ │ +foo() -> int_to_meter(24).

          A type checker that performs nominal type-checking should raise a warning. │ │ │ │ According to the specification, foo/0 should return a foot() type. However, │ │ │ │ the function int_to_meter/1 returns a meter() type, so foo/0 will also │ │ │ │ return a meter() type. Because meter() and foot() are incompatible │ │ │ │ nominal types, Dialyzer raises the following warning for foo/0:

          Invalid type specification for function foo/0.
          │ │ │ │ -The success typing is foo() -> (meter() :: integer())
          │ │ │ │ -But the spec is foo() -> foot()
          │ │ │ │ +The success typing is foo() -> (meter() :: integer())
          │ │ │ │ +But the spec is foo() -> foot()
          │ │ │ │  The return types do not overlap

          On the other hand, a nominal type is compatible with a non-opaque, non-nominal │ │ │ │ type with the same structure. This compatibility goes both ways, meaning that │ │ │ │ passing a structural type when a nominal type is expected is allowed, and │ │ │ │ -vice versa.

          -spec qaz() -> integer().
          │ │ │ │ -qaz() -> int_to_meter(24).

          A type checker that performs nominal type-checking should not raise a warning │ │ │ │ +vice versa.

          -spec qaz() -> integer().
          │ │ │ │ +qaz() -> int_to_meter(24).

          A type checker that performs nominal type-checking should not raise a warning │ │ │ │ in this case. The specification says that qaz/0 should return an integer() │ │ │ │ type. However, the function int_to_meter/1 returns a meter() type, so │ │ │ │ qaz/0 will also return a meter() type. integer() is not a nominal type. │ │ │ │ The structure of meter() is compatible with integer(). Dialyzer can │ │ │ │ analyze the function above without raising a warning.

          There is one exception where two nominal types with different names can be │ │ │ │ compatible: when one is derived from the other. For nominal types s() and │ │ │ │ -t(), s() can be derived from t() in the two following ways:

          1. If s() is directly derived from t().
          -nominal s() :: t().
          1. If s() is derived from other nominal types, which are derived from t().
          -nominal s() :: nominal_1().
          │ │ │ │ --nominal nominal_1() :: nominal_2().
          │ │ │ │ --nominal nominal_2() :: t().

          In both cases, s() and t() are compatible nominal types even though they │ │ │ │ +t(), s() can be derived from t() in the two following ways:

          1. If s() is directly derived from t().
          -nominal s() :: t().
          1. If s() is derived from other nominal types, which are derived from t().
          -nominal s() :: nominal_1().
          │ │ │ │ +-nominal nominal_1() :: nominal_2().
          │ │ │ │ +-nominal nominal_2() :: t().

          In both cases, s() and t() are compatible nominal types even though they │ │ │ │ have different names. Defining them in different modules does not affect │ │ │ │ compatiblity.

          In summary, nominal type-checking rules are as follows:

          A function that has a -spec that states an argument or a return type to be │ │ │ │ nominal type a/0 (or any other arity), accepts or may return:

          • Nominal type a/0
          • A compatible nominal type b/0
          • A compatible structural type

          A function that has a -spec that states an argument or a return type to be a │ │ │ │ structural type b/0 (or any other arity), accepts or may return:

          • A compatible structural type
          • A compatible nominal type

          When deciding if a type should be nominal, here are some suggestions:

          • If there are other types in the same module with the same structure, and they │ │ │ │ should never be mixed, all of them can benefit from being nominal types.
          • If a type represents a unit like meter, second, byte, and so on, defining it │ │ │ │ as a nominal type is always more useful than -type. You get the nice │ │ │ │ guarantee that you cannot mix them up with other units defined as nominal │ │ │ ├── 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.

      │ │ │ │ │ │ │ │ @@ -315,29 +315,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:put/3 │ │ │ │

      │ │ │ │

      maps:put/3 is implemented in C.

      If the key is known to already exist in the map, maps:update/3 is slightly │ │ │ │ more efficient than maps:put/3.

      If the compiler can determine that the third argument is always a map, it │ │ │ │ -will rewrite the call to maps:put/3 to use the map syntax for updating the map.

      For example, consider the following function:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map2 = maps:put(b, B, Map1),
      │ │ │ │ -    maps:put(c, C, Map2).

      The compiler first rewrites each call to maps:put/3 to use the map │ │ │ │ +will rewrite the call to maps:put/3 to use the map syntax for updating the map.

      For example, consider the following function:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map2 = maps:put(b, B, Map1),
      │ │ │ │ +    maps:put(c, C, Map2).

      The compiler first rewrites each call to maps:put/3 to use the map │ │ │ │ syntax, and subsequently combines the three update operations to a │ │ │ │ -single update operation:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ -    Map0#{a => A, b => B, c => C}.

      If the compiler cannot determine that the third argument is always a │ │ │ │ +single update operation:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ +    Map0#{a => A, b => B, c => C}.

      If the compiler cannot determine that the third argument is always a │ │ │ │ map, it retains the maps:put/3 call. For example, given this │ │ │ │ -function:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map2 = maps:put(b, B, Map1),
      │ │ │ │ -    maps:put(c, C, Map2).

      the compiler keeps the first call to maps:put/3, but rewrites │ │ │ │ -and combines the other two calls:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map1#{b => B, c => C}.

      Change

      The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ │ +function:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map2 = maps:put(b, B, Map1),
      │ │ │ │ +    maps:put(c, C, Map2).

      the compiler keeps the first call to maps:put/3, but rewrites │ │ │ │ +and combines the other two calls:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map1#{b => B, c => C}.

      Change

      The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ │ Erlang/OTP 28.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:remove/2 │ │ │ │

      │ │ │ │

      maps:remove/2 is implemented in C.

      │ │ │ ├── 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,36 +22,36 @@ │ │ │ │ │ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 in two ways. 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}]

Alternatively, two lists can be zipped together using a zip generator as │ │ │ │ -follows:

> [{X, Y} || X <:- [1,2,3] && Y <:- [a,b,c]].
│ │ │ │ -[{1,a},{2,b},{3,c}]

Change

Strict generators are used by default in the examples. More details and │ │ │ │ +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 in two ways. 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}]

Alternatively, two lists can be zipped together using a zip generator as │ │ │ │ +follows:

> [{X, Y} || X <:- [1,2,3] && Y <:- [a,b,c]].
│ │ │ │ +[{1,a},{2,b},{3,c}]

Change

Strict generators are used by default in the examples. More details and │ │ │ │ comparisons can be found in Strict and Relaxed Generators.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ │ @@ -59,127 +59,127 @@ │ │ │ │ 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)].
│ │ │ │ -zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

│ │ │ │ +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)].
│ │ │ │ +zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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.
  • Within a zip generator, binding of all variables happen at the same time.

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, ...]
│ │ │ │      ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Strict and Relaxed Generators │ │ │ │

│ │ │ │

Strict and relaxed generators have different behaviors when the right-hand │ │ │ │ side expression does not match the left-hand side pattern. A relaxed generator │ │ │ │ ignores that term and continues on. A strict generator fails with an exception.

Their difference can be shown in the following example. The generator │ │ │ │ expects a two-tuple pattern. If a relaxed generator is used, b will be │ │ │ │ silently skipped. If a strict generator is used, an exception will be raised │ │ │ │ -when the pattern matching fails with b.

{_,_} <-  [{ok, a}, b]
│ │ │ │ -{_,_} <:- [{ok, a}, b]

Semantically, strict or relaxed generators convey different intentions from │ │ │ │ +when the pattern matching fails with b.

{_,_} <-  [{ok, a}, b]
│ │ │ │ +{_,_} <:- [{ok, a}, b]

Semantically, strict or relaxed generators convey different intentions from │ │ │ │ the programmer. Strict generators are used when unexpected elements in the │ │ │ │ input data should not be tolerated. Any element not conforming to specific │ │ │ │ patterns should immediately crash the comprehension, because the program may │ │ │ │ not be prepared to handle it.

For example, the following comprehension is rewritten from one in the Erlang │ │ │ │ linter. It extracts arities from all defined functions. All elements in the │ │ │ │ list DefinedFuns are two-tuples, containing name and arity for functions. │ │ │ │ If any of them differs from this pattern, it means that something has added │ │ │ │ an invalid item into the list of defined functions. It is better for the linter │ │ │ │ to crash in the comprehension than skipping the invalid item and continue │ │ │ │ running. Using a strict generator here is correct, because the linter should │ │ │ │ -not hide the presence of an internal inconsistency.

[Arity || {_FunName, Arity} <:- DefinedFuns]

In contrast, relaxed generators are used when unexpected elements in the input │ │ │ │ +not hide the presence of an internal inconsistency.

[Arity || {_FunName, Arity} <:- DefinedFuns]

In contrast, relaxed generators are used when unexpected elements in the input │ │ │ │ data should be filtered out. The programmer is aware that some elements │ │ │ │ may not conform to specific patterns. Those elements can be safely excluded │ │ │ │ from the comprehension result.

For example, the following comprehension is from a compiler module that │ │ │ │ transforms normal Erlang code to Core Erlang. It finds all defined functions │ │ │ │ from an abstract form, and output them in two-tuples, each containing name and │ │ │ │ arity of a function. Not all forms are function declarations. All the forms │ │ │ │ that are not function declarations should be ignored by this comprehensions. │ │ │ │ Using a relaxed generator here is correct, because the programmer intends to │ │ │ │ -exclude all elements with other patterns.

[{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

Strict and relaxed generators don't always have distinct use cases. When the │ │ │ │ +exclude all elements with other patterns.

[{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

Strict and relaxed generators don't always have distinct use cases. When the │ │ │ │ left-hand side pattern of a generator is a fresh variable, pattern matching │ │ │ │ cannot fail. Using either strict or relaxed generators leads to the same │ │ │ │ behavior. While the preference and use cases might be individual, it is │ │ │ │ recommended to use strict generators when either can be used. Using strict │ │ │ │ generators by default aligns with Erlang's "Let it crash" philosophy.

│ │ │ │ │ │ │ │ │ │ │ ├── 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 28 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 │ │ │ │ @@ -78,72 +78,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 succinct way using nested case expressions:

case a() of
│ │ │ │ -    {ok, A} ->
│ │ │ │ +expression fails to match the pattern.

The example can be written in a less succinct 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,18 +386,18 @@ │ │ │ │ {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

The receive expression searches for a message in the message queue that match │ │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ │ the clauses is matched against a message from top to bottom. The first message, │ │ │ │ from the start of the message queue, that matches will be selected. Messages are │ │ │ │ normally │ │ │ │ enqueued in the message queue in │ │ │ │ @@ -414,27 +414,27 @@ │ │ │ │ specific messages and the message queue is huge, executing such a receive │ │ │ │ expression might become very expensive.

One type of receive expressions matching on only specific patterns can, │ │ │ │ however, be optimized by the compiler and runtime system. This in the scenario │ │ │ │ where you create a reference and │ │ │ │ match on it in all clauses of a receive expression close to where the │ │ │ │ reference was created. In this case only the amount of messages received after │ │ │ │ the reference was created needs to be inspected. For more information see the │ │ │ │ -Fetching Received Messages section of the Efficiency Guide.

Example:

wait_for_onhook() ->
│ │ │ │ +Fetching Received Messages section of the Efficiency Guide.

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 │ │ │ │ @@ -445,35 +445,35 @@ │ │ │ │ another short timeout) might be cheap since the timeout is short. This is │ │ │ │ not necessarily the case. If the patterns in the clauses of the receive │ │ │ │ expression only match specific messages and no such messages exist in the │ │ │ │ message queue, the whole message queue needs to be inspected before the │ │ │ │ timeout can occur. That is, the same apply as in │ │ │ │ the warning above.

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 │ │ │ │ @@ -515,21 +515,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.

│ │ │ │ @@ -555,15 +555,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 │ │ │ │ @@ -582,136 +582,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 │ │ │ │ @@ -722,34 +722,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.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -774,41 +774,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 │ │ │ │

│ │ │ │ @@ -824,78 +824,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.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -905,35 +905,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 │ │ │ │ @@ -943,47 +943,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 │ │ │ │ @@ -1006,40 +1006,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
│ │ │ │ @@ -1051,82 +1051,82 @@
│ │ │ │    
│ │ │ │      
│ │ │ │    
│ │ │ │    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 four kinds of generators. Three of them have a relaxed and a strict │ │ │ │ variant. The fourth kind of generator, zip generator, is composed by two or │ │ │ │ more non-zip generators.

Change

Strict generators and zip generators were introduced in Erlang/OTP 28. │ │ │ │ Using strict generators is a better practice when either strict or relaxed │ │ │ │ generators work. More details are in │ │ │ │ Programming Examples.

A list generator has the following syntax for relaxed:

Pattern <- ListExpr

and strict variant:

Pattern <:- ListExpr

where ListExpr is an expression that evaluates to a list of terms.

A bit string generator has the following syntax for relaxed:

BitstringPattern <= BitStringExpr

and strict variant:

BitstringPattern <:= BitStringExpr

where BitStringExpr is an expression that evaluates to a bit string.

A map generator has the following syntax for relaxed:

KeyPattern := ValuePattern <- MapExpression

and strict variant:

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 zip generator has the following syntax:

Generator_1 && ... && Generator_n

where every Generator_i is a non-zip generator. Generators within a zip │ │ │ │ generator are treated as one generator and evaluated in parallel.

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}]

Filtering a list, crashing when the element is not a 2-tuple:

1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ -** exception error: no match of right hand side value [a]

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}]

Combining elements from two list generators, using a zip generator:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
│ │ │ │ -[{a,1},{b,2},{c,3}]

Combining elements from two list generators using a zip generator, filtering │ │ │ │ -out odd numbers:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
│ │ │ │ -[{a,1},{b,2},{c,3}]

Filtering out non-matching elements from two lists.

1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
│ │ │ │ -[1,3]

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}]

Filtering a list, crashing when the element is not a 2-tuple:

1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ +** exception error: no match of right hand side value [a]

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}]

Combining elements from two list generators, using a zip generator:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
│ │ │ │ +[{a,1},{b,2},{c,3}]

Combining elements from two list generators using a zip generator, filtering │ │ │ │ +out odd numbers:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
│ │ │ │ +[{a,1},{b,2},{c,3}]

Filtering out non-matching elements from two lists.

1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
│ │ │ │ +[1,3]

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 │ │ │ │ @@ -95,30 +95,30 @@ │ │ │ │ │ │ │ │ Fetching Received Messages │ │ │ │ │ │ │ │

The cost of fetching a received message from the message queue 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 guaranteed that the optimization │ │ │ │ will take, but what about more complicated code?

│ │ │ │ │ │ │ │ @@ -134,101 +134,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,96 +108,96 @@
│ │ │ │  

There are three reserved metadata keys for -moduledoc:

  • since => unicode:chardata() - 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 => unicode:chardata() - Shows a text in the documentation explaining that it is │ │ │ │ deprecated and what to use instead.
  • format => unicode:chardata() - 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.

  • group => unicode:chardata() - A group that the function, type or callback belongs to. │ │ │ │ It allows tooling, such as shell autocompletion and documentation generators, to list all │ │ │ │ entries within the same group together, often using the group name as an indicator.

  • 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 │ │ │ │ @@ -282,21 +282,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 │ │ │ │ @@ -104,18 +104,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 │ │ │ │ @@ -149,52 +149,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 │ │ │ │

    │ │ │ │ @@ -212,94 +212,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 │ │ │ │

    │ │ │ │ @@ -419,41 +419,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 │ │ │ │ @@ -471,44 +471,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 - 28.3.1 │ │ │ │ │ - urn:uuid:9ae0b2f7-8f36-ceec-e8d9-e41a69903af2 │ │ │ │ │ + urn:uuid:d637c0c9-9afb-19a6-f55c-4450096fba5b │ │ │ │ │ en │ │ │ │ │ - 2026-01-15T13:53:39Z │ │ │ │ │ + 2026-03-02T07:59:02Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -86,22 +86,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── 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 │ │ │ │ @@ -146,18 +146,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Compiler optimizations of setelement/3 │ │ │ │

    │ │ │ │

    Under certain conditions, the compiler can coalesce multiple calls to │ │ │ │ setelement/3 into a single operation, avoiding │ │ │ │ -the cost of copying the tuple for each call.

    For example:

    multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
    │ │ │ │ -    T1 = setelement(5, T0, new_value),
    │ │ │ │ -    T2 = setelement(7, T1, foobar),
    │ │ │ │ -    setelement(9, T2, bar).

    The compiler will replace the three setelement/3 calls with code that │ │ │ │ +the cost of copying the tuple for each call.

    For example:

    multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
    │ │ │ │ +    T1 = setelement(5, T0, new_value),
    │ │ │ │ +    T2 = setelement(7, T1, foobar),
    │ │ │ │ +    setelement(9, T2, bar).

    The compiler will replace the three setelement/3 calls with code that │ │ │ │ copies the tuple once and updates the elements at positions 5, 7, and 9.

    Starting with Erlang/OTP 26, the following conditions must be met for │ │ │ │ setelement/3 calls to be coalesced into a single │ │ │ │ operation:

    • The tuple argument must be known at compile time to be a tuple of a │ │ │ │ specific size.

    • The element indices must be integer literals, not variables or expressions.

    • There must be no intervening expressions between the calls to │ │ │ │ setelement/3.

    • The tuple returned from one setelement/3 call must be │ │ │ │ used only in the subsequent setelement/3 call.

    Before Erlang/OTP 26, an additional condition was that │ │ │ │ setelement/3 calls had to be made in descending │ │ │ ├── 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 │ │ │ @@ -241,18 +241,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ Compiler optimizations of setelement/3 │ │ │

          │ │ │

          Under certain conditions, the compiler can coalesce multiple calls to │ │ │ setelement/3 into a single operation, avoiding │ │ │ -the cost of copying the tuple for each call.

          For example:

          multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
          │ │ │ -    T1 = setelement(5, T0, new_value),
          │ │ │ -    T2 = setelement(7, T1, foobar),
          │ │ │ -    setelement(9, T2, bar).

          The compiler will replace the three setelement/3 calls with code that │ │ │ +the cost of copying the tuple for each call.

          For example:

          multiple_setelement(T0) when tuple_size(T0) =:= 9 ->
          │ │ │ +    T1 = setelement(5, T0, new_value),
          │ │ │ +    T2 = setelement(7, T1, foobar),
          │ │ │ +    setelement(9, T2, bar).

          The compiler will replace the three setelement/3 calls with code that │ │ │ copies the tuple once and updates the elements at positions 5, 7, and 9.

          Starting with Erlang/OTP 26, the following conditions must be met for │ │ │ setelement/3 calls to be coalesced into a single │ │ │ operation:

          • The tuple argument must be known at compile time to be a tuple of a │ │ │ specific size.

          • The element indices must be integer literals, not variables or expressions.

          • There must be no intervening expressions between the calls to │ │ │ setelement/3.

          • The tuple returned from one setelement/3 call must be │ │ │ used only in the subsequent setelement/3 call.

          Before Erlang/OTP 26, an additional condition was that │ │ │ setelement/3 calls had to be made in descending │ │ ├── ./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 │ │ │ @@ -244,52 +244,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 │ │ │

    │ │ │ @@ -307,94 +307,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 │ │ │

    │ │ │ @@ -514,41 +514,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 │ │ │ @@ -566,47 +566,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,96 +203,96 @@
    │ │ │  

    There are three reserved metadata keys for -moduledoc:

    • since => unicode:chardata() - 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 => unicode:chardata() - Shows a text in the documentation explaining that it is │ │ │ deprecated and what to use instead.
    • format => unicode:chardata() - 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.

    • group => unicode:chardata() - A group that the function, type or callback belongs to. │ │ │ It allows tooling, such as shell autocompletion and documentation generators, to list all │ │ │ entries within the same group together, often using the group name as an indicator.

    • 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 │ │ │ @@ -377,21 +377,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 │ │ │ @@ -190,30 +190,30 @@ │ │ │ │ │ │ Fetching Received Messages │ │ │ │ │ │

    The cost of fetching a received message from the message queue 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 guaranteed that the optimization │ │ │ will take, but what about more complicated code?

    │ │ │ │ │ │ @@ -229,101 +229,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 succinct way using nested case expressions:

    case a() of
    │ │ │ -    {ok, A} ->
    │ │ │ +expression fails to match the pattern.

    The example can be written in a less succinct 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,18 +481,18 @@ │ │ │ {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

    The receive expression searches for a message in the message queue that match │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ the clauses is matched against a message from top to bottom. The first message, │ │ │ from the start of the message queue, that matches will be selected. Messages are │ │ │ normally │ │ │ enqueued in the message queue in │ │ │ @@ -509,27 +509,27 @@ │ │ │ specific messages and the message queue is huge, executing such a receive │ │ │ expression might become very expensive.

    One type of receive expressions matching on only specific patterns can, │ │ │ however, be optimized by the compiler and runtime system. This in the scenario │ │ │ where you create a reference and │ │ │ match on it in all clauses of a receive expression close to where the │ │ │ reference was created. In this case only the amount of messages received after │ │ │ the reference was created needs to be inspected. For more information see the │ │ │ -Fetching Received Messages section of the Efficiency Guide.

    Example:

    wait_for_onhook() ->
    │ │ │ +Fetching Received Messages section of the Efficiency Guide.

    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 │ │ │ @@ -540,35 +540,35 @@ │ │ │ another short timeout) might be cheap since the timeout is short. This is │ │ │ not necessarily the case. If the patterns in the clauses of the receive │ │ │ expression only match specific messages and no such messages exist in the │ │ │ message queue, the whole message queue needs to be inspected before the │ │ │ timeout can occur. That is, the same apply as in │ │ │ the warning above.

    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 │ │ │ @@ -610,21 +610,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.

    │ │ │ @@ -650,15 +650,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 │ │ │ @@ -677,136 +677,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 │ │ │ @@ -817,34 +817,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.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -869,41 +869,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 │ │ │

    │ │ │ @@ -919,78 +919,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.

    │ │ │ │ │ │ │ │ │ @@ -1000,35 +1000,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 │ │ │ @@ -1038,47 +1038,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 │ │ │ @@ -1101,40 +1101,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
    │ │ │ @@ -1146,82 +1146,82 @@
    │ │ │    
    │ │ │      
    │ │ │    
    │ │ │    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 four kinds of generators. Three of them have a relaxed and a strict │ │ │ variant. The fourth kind of generator, zip generator, is composed by two or │ │ │ more non-zip generators.

    Change

    Strict generators and zip generators were introduced in Erlang/OTP 28. │ │ │ Using strict generators is a better practice when either strict or relaxed │ │ │ generators work. More details are in │ │ │ Programming Examples.

    A list generator has the following syntax for relaxed:

    Pattern <- ListExpr

    and strict variant:

    Pattern <:- ListExpr

    where ListExpr is an expression that evaluates to a list of terms.

    A bit string generator has the following syntax for relaxed:

    BitstringPattern <= BitStringExpr

    and strict variant:

    BitstringPattern <:= BitStringExpr

    where BitStringExpr is an expression that evaluates to a bit string.

    A map generator has the following syntax for relaxed:

    KeyPattern := ValuePattern <- MapExpression

    and strict variant:

    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 zip generator has the following syntax:

    Generator_1 && ... && Generator_n

    where every Generator_i is a non-zip generator. Generators within a zip │ │ │ generator are treated as one generator and evaluated in parallel.

    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}]

    Filtering a list, crashing when the element is not a 2-tuple:

    1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ -** exception error: no match of right hand side value [a]

    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}]

    Combining elements from two list generators, using a zip generator:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
    │ │ │ -[{a,1},{b,2},{c,3}]

    Combining elements from two list generators using a zip generator, filtering │ │ │ -out odd numbers:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
    │ │ │ -[{a,1},{b,2},{c,3}]

    Filtering out non-matching elements from two lists.

    1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
    │ │ │ -[1,3]

    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}]

    Filtering a list, crashing when the element is not a 2-tuple:

    1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ +** exception error: no match of right hand side value [a]

    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}]

    Combining elements from two list generators, using a zip generator:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
    │ │ │ +[{a,1},{b,2},{c,3}]

    Combining elements from two list generators using a zip generator, filtering │ │ │ +out odd numbers:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
    │ │ │ +[{a,1},{b,2},{c,3}]

    Filtering out non-matching elements from two lists.

    1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
    │ │ │ +[1,3]

    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,36 +117,36 @@ │ │ │ │ │ │

      │ │ │ │ │ │ │ │ │ │ │ │ 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 in two ways. 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}]

      Alternatively, two lists can be zipped together using a zip generator as │ │ │ -follows:

      > [{X, Y} || X <:- [1,2,3] && Y <:- [a,b,c]].
      │ │ │ -[{1,a},{2,b},{3,c}]

      Change

      Strict generators are used by default in the examples. More details and │ │ │ +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 in two ways. 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}]

      Alternatively, two lists can be zipped together using a zip generator as │ │ │ +follows:

      > [{X, Y} || X <:- [1,2,3] && Y <:- [a,b,c]].
      │ │ │ +[{1,a},{2,b},{3,c}]

      Change

      Strict generators are used by default in the examples. More details and │ │ │ comparisons can be found in Strict and Relaxed Generators.

      │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ @@ -154,127 +154,127 @@ │ │ │ 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)].
      │ │ │ -zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

      │ │ │ +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)].
      │ │ │ +zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

      │ │ │ │ │ │ │ │ │ │ │ │ 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.
      • Within a zip generator, binding of all variables happen at the same time.

      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, ...]
      │ │ │      ...

      │ │ │ │ │ │ │ │ │ │ │ │ Strict and Relaxed Generators │ │ │

      │ │ │

      Strict and relaxed generators have different behaviors when the right-hand │ │ │ side expression does not match the left-hand side pattern. A relaxed generator │ │ │ ignores that term and continues on. A strict generator fails with an exception.

      Their difference can be shown in the following example. The generator │ │ │ expects a two-tuple pattern. If a relaxed generator is used, b will be │ │ │ silently skipped. If a strict generator is used, an exception will be raised │ │ │ -when the pattern matching fails with b.

      {_,_} <-  [{ok, a}, b]
      │ │ │ -{_,_} <:- [{ok, a}, b]

      Semantically, strict or relaxed generators convey different intentions from │ │ │ +when the pattern matching fails with b.

      {_,_} <-  [{ok, a}, b]
      │ │ │ +{_,_} <:- [{ok, a}, b]

      Semantically, strict or relaxed generators convey different intentions from │ │ │ the programmer. Strict generators are used when unexpected elements in the │ │ │ input data should not be tolerated. Any element not conforming to specific │ │ │ patterns should immediately crash the comprehension, because the program may │ │ │ not be prepared to handle it.

      For example, the following comprehension is rewritten from one in the Erlang │ │ │ linter. It extracts arities from all defined functions. All elements in the │ │ │ list DefinedFuns are two-tuples, containing name and arity for functions. │ │ │ If any of them differs from this pattern, it means that something has added │ │ │ an invalid item into the list of defined functions. It is better for the linter │ │ │ to crash in the comprehension than skipping the invalid item and continue │ │ │ running. Using a strict generator here is correct, because the linter should │ │ │ -not hide the presence of an internal inconsistency.

      [Arity || {_FunName, Arity} <:- DefinedFuns]

      In contrast, relaxed generators are used when unexpected elements in the input │ │ │ +not hide the presence of an internal inconsistency.

      [Arity || {_FunName, Arity} <:- DefinedFuns]

      In contrast, relaxed generators are used when unexpected elements in the input │ │ │ data should be filtered out. The programmer is aware that some elements │ │ │ may not conform to specific patterns. Those elements can be safely excluded │ │ │ from the comprehension result.

      For example, the following comprehension is from a compiler module that │ │ │ transforms normal Erlang code to Core Erlang. It finds all defined functions │ │ │ from an abstract form, and output them in two-tuples, each containing name and │ │ │ arity of a function. Not all forms are function declarations. All the forms │ │ │ that are not function declarations should be ignored by this comprehensions. │ │ │ Using a relaxed generator here is correct, because the programmer intends to │ │ │ -exclude all elements with other patterns.

      [{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

      Strict and relaxed generators don't always have distinct use cases. When the │ │ │ +exclude all elements with other patterns.

      [{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

      Strict and relaxed generators don't always have distinct use cases. When the │ │ │ left-hand side pattern of a generator is a fresh variable, pattern matching │ │ │ cannot fail. Using either strict or relaxed generators leads to the same │ │ │ behavior. While the preference and use cases might be individual, it is │ │ │ recommended to use strict generators when either can be used. Using strict │ │ │ generators by default aligns with Erlang's "Let it crash" philosophy.

      │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/listhandling.html │ │ │ @@ -120,105 +120,105 @@ │ │ │ │ │ │ │ │ │ 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.

    │ │ │ │ │ │ @@ -410,29 +410,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ maps:put/3 │ │ │

    │ │ │

    maps:put/3 is implemented in C.

    If the key is known to already exist in the map, maps:update/3 is slightly │ │ │ more efficient than maps:put/3.

    If the compiler can determine that the third argument is always a map, it │ │ │ -will rewrite the call to maps:put/3 to use the map syntax for updating the map.

    For example, consider the following function:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map2 = maps:put(b, B, Map1),
    │ │ │ -    maps:put(c, C, Map2).

    The compiler first rewrites each call to maps:put/3 to use the map │ │ │ +will rewrite the call to maps:put/3 to use the map syntax for updating the map.

    For example, consider the following function:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map2 = maps:put(b, B, Map1),
    │ │ │ +    maps:put(c, C, Map2).

    The compiler first rewrites each call to maps:put/3 to use the map │ │ │ syntax, and subsequently combines the three update operations to a │ │ │ -single update operation:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ -    Map0#{a => A, b => B, c => C}.

    If the compiler cannot determine that the third argument is always a │ │ │ +single update operation:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ +    Map0#{a => A, b => B, c => C}.

    If the compiler cannot determine that the third argument is always a │ │ │ map, it retains the maps:put/3 call. For example, given this │ │ │ -function:

    add_to_map(Map0, A, B, C) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map2 = maps:put(b, B, Map1),
    │ │ │ -    maps:put(c, C, Map2).

    the compiler keeps the first call to maps:put/3, but rewrites │ │ │ -and combines the other two calls:

    add_to_map(Map0, A, B, C) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map1#{b => B, c => C}.

    Change

    The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ +function:

    add_to_map(Map0, A, B, C) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map2 = maps:put(b, B, Map1),
    │ │ │ +    maps:put(c, C, Map2).

    the compiler keeps the first call to maps:put/3, but rewrites │ │ │ +and combines the other two calls:

    add_to_map(Map0, A, B, C) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map1#{b => B, c => C}.

    Change

    The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ Erlang/OTP 28.

    │ │ │ │ │ │ │ │ │ │ │ │ maps:remove/2 │ │ │

    │ │ │

    maps:remove/2 is implemented in C.

    │ │ ├── ./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/nominals.html │ │ │ @@ -123,55 +123,55 @@ │ │ │ │ │ │

    For user-defined types │ │ │ defined with -type, the Erlang compiler will ignore their type names. This │ │ │ means the Erlang compiler uses a structural type system. Two types are seen as │ │ │ equivalent if their structures are the same. Type comparison is based on the │ │ │ structures of the types, not on how the user explicitly defines them. In the │ │ │ following example, meter() and foot() are equivalent, and neither differs │ │ │ -from the basic type integer().

    -type meter() :: integer().
    │ │ │ --type foot() :: integer().

    Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ +from the basic type integer().

    -type meter() :: integer().
    │ │ │ +-type foot() :: integer().

    Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ if and only if they are declared with the same type name. The syntax for │ │ │ declaring nominal types is -nominal.

    If meter() and foot() are defined as nominal types, they will no longer be │ │ │ compatible. When a function expects type meter(), passing in type foot() │ │ │ -will result in a warning raised by the type checker.

    -nominal meter() :: integer().
    │ │ │ --nominal foot() :: integer().

    The main use case of nominal types is to prevent accidental misuse of types with │ │ │ +will result in a warning raised by the type checker.

    -nominal meter() :: integer().
    │ │ │ +-nominal foot() :: integer().

    The main use case of nominal types is to prevent accidental misuse of types with │ │ │ the same structure. Within OTP, nominal type-checking is done in Dialyzer. The │ │ │ Erlang compiler does not perform nominal type-checking.

    │ │ │ │ │ │ │ │ │ │ │ │ Nominal Type-Checking Rules │ │ │

    │ │ │

    In general, if two nominal types have different names, and one is not derived │ │ │ from the other, they are not compatible. Dialyzer's nominal type-checking │ │ │ -aligns with the examples' expected results in this section.

    If we continue from the example above:

    -spec int_to_meter(integer()) -> meter().
    │ │ │ -int_to_meter(X) -> X.
    │ │ │ +aligns with the examples' expected results in this section.

    If we continue from the example above:

    -spec int_to_meter(integer()) -> meter().
    │ │ │ +int_to_meter(X) -> X.
    │ │ │  
    │ │ │ --spec foo() -> foot().
    │ │ │ -foo() -> int_to_meter(24).

    A type checker that performs nominal type-checking should raise a warning. │ │ │ +-spec foo() -> foot(). │ │ │ +foo() -> int_to_meter(24).

    A type checker that performs nominal type-checking should raise a warning. │ │ │ According to the specification, foo/0 should return a foot() type. However, │ │ │ the function int_to_meter/1 returns a meter() type, so foo/0 will also │ │ │ return a meter() type. Because meter() and foot() are incompatible │ │ │ nominal types, Dialyzer raises the following warning for foo/0:

    Invalid type specification for function foo/0.
    │ │ │ -The success typing is foo() -> (meter() :: integer())
    │ │ │ -But the spec is foo() -> foot()
    │ │ │ +The success typing is foo() -> (meter() :: integer())
    │ │ │ +But the spec is foo() -> foot()
    │ │ │  The return types do not overlap

    On the other hand, a nominal type is compatible with a non-opaque, non-nominal │ │ │ type with the same structure. This compatibility goes both ways, meaning that │ │ │ passing a structural type when a nominal type is expected is allowed, and │ │ │ -vice versa.

    -spec qaz() -> integer().
    │ │ │ -qaz() -> int_to_meter(24).

    A type checker that performs nominal type-checking should not raise a warning │ │ │ +vice versa.

    -spec qaz() -> integer().
    │ │ │ +qaz() -> int_to_meter(24).

    A type checker that performs nominal type-checking should not raise a warning │ │ │ in this case. The specification says that qaz/0 should return an integer() │ │ │ type. However, the function int_to_meter/1 returns a meter() type, so │ │ │ qaz/0 will also return a meter() type. integer() is not a nominal type. │ │ │ The structure of meter() is compatible with integer(). Dialyzer can │ │ │ analyze the function above without raising a warning.

    There is one exception where two nominal types with different names can be │ │ │ compatible: when one is derived from the other. For nominal types s() and │ │ │ -t(), s() can be derived from t() in the two following ways:

    1. If s() is directly derived from t().
    -nominal s() :: t().
    1. If s() is derived from other nominal types, which are derived from t().
    -nominal s() :: nominal_1().
    │ │ │ --nominal nominal_1() :: nominal_2().
    │ │ │ --nominal nominal_2() :: t().

    In both cases, s() and t() are compatible nominal types even though they │ │ │ +t(), s() can be derived from t() in the two following ways:

    1. If s() is directly derived from t().
    -nominal s() :: t().
    1. If s() is derived from other nominal types, which are derived from t().
    -nominal s() :: nominal_1().
    │ │ │ +-nominal nominal_1() :: nominal_2().
    │ │ │ +-nominal nominal_2() :: t().

    In both cases, s() and t() are compatible nominal types even though they │ │ │ have different names. Defining them in different modules does not affect │ │ │ compatiblity.

    In summary, nominal type-checking rules are as follows:

    A function that has a -spec that states an argument or a return type to be │ │ │ nominal type a/0 (or any other arity), accepts or may return:

    • Nominal type a/0
    • A compatible nominal type b/0
    • A compatible structural type

    A function that has a -spec that states an argument or a return type to be a │ │ │ structural type b/0 (or any other arity), accepts or may return:

    • A compatible structural type
    • A compatible nominal type

    When deciding if a type should be nominal, here are some suggestions:

    • If there are other types in the same module with the same structure, and they │ │ │ should never be mixed, all of them can benefit from being nominal types.
    • If a type represents a unit like meter, second, byte, and so on, defining it │ │ │ as a nominal type is always more useful than -type. You get the nice │ │ │ guarantee that you cannot mix them up with other units defined as nominal │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/opaques.html │ │ │ @@ -124,24 +124,24 @@ │ │ │

      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.

      Change

      Since Erlang/OTP 28, Dialyzer checks opaques in their defining module in the │ │ │ same way as nominals. Outside of the defining module, Dialyzer checks │ │ │ opaques for opacity violations.

      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 . One exception │ │ │ is that =:= and =/= can be used between two opaques with the same name, or │ │ │ between an opaque and any(), as those comparisons do not reveal underlying │ │ │ types.
      • Use functions provided by the module for working with the type. For │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ ├── ./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/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).
    │ │ │ -ok
    │ │ │ -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 => []}}}]}]]}

    │ │ │ +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). │ │ │ +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 => []}}}]}]]}

    │ │ │ │ │ │ │ │ │ │ │ │ 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).
    │ │ │ -ok
    │ │ │ -3> sys:trace(ch4, true).
    │ │ │ -ok
    │ │ │ -4> ch4:alloc().
    │ │ │ -ch4 event = {in,alloc,<0.88.0>}
    │ │ │ -ch4 event = {out,{ch4,1},<0.88.0>}
    │ │ │ +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).
    │ │ │ +ok
    │ │ │ +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).
    │ │ │ -ok
    │ │ │ -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|...]}]}

    │ │ │ +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). │ │ │ +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|...]}]}

    │ │ │ │ │ │ │ │ │ │ │ │ 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, -opaque, and │ │ │ --nominal attributes as in the following example:

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

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ +-nominal attributes as in the following example:

        -type my_struct_type() :: Type.
        │ │ │ +-opaque my_opaq_type() :: Type.
        │ │ │ +-nominal my_nominal_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.

        Change

        Nominal types were introduced in Erlang/OTP 28.

        Types declared as nominal are type-checked according to the user-defined │ │ │ names instead of their structure. That is, -nominal feet() :: integer() and │ │ │ -nominal meter() :: integer() are not the same type, while if -type is │ │ │ used it would be.

        Read more on Opaques and Nominals

        │ │ │ │ │ │ │ │ │ │ │ │ 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/system/vulnerabilities.html │ │ │ @@ -143,69 +143,69 @@ │ │ │ vulnerability (affected), the affected Erlang/OTP releases, namely 28.0, │ │ │ 28.0.1, and 28.0.2, and the Erlang/OTP application that was vulnerable │ │ │ in application version ssh@5.3, ssh@5.3.1, and ssh@5.3.2. │ │ │ Erlang/OTP reports the affected versions using the release and the │ │ │ application versions because it is possible to update the application independently │ │ │ from the release. │ │ │ In some cases, there may be an optional action statement that describes a workaround │ │ │ -to avoid the mentioned vulnerability.

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +to avoid the mentioned vulnerability.

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2025-48038"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-09-16T08:22:13.223967395Z",
        │ │ │ -  "products": [
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.1" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.2" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.1" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.2" }
        │ │ │ +  ],
        │ │ │    "status": "affected",
        │ │ │    "action_statement": "Update to any of the following versions: pkg:otp/ssh@5.3.3",
        │ │ │    "action_statement_timestamp": "2025-09-16T08:22:13.223967395Z"
        │ │ │ -},

        Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ +},

        Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ As an example, there is a new statement for CVE-2025-48038 with status fixed, │ │ │ that links to the first release that do not suffer from CVE-2025-48038, namely │ │ │ -OTP version 28.0.3 and application ssh@5.3.3.

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +OTP version 28.0.3 and application ssh@5.3.3. 

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2025-48038"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-09-16T08:22:13.241103494Z",
        │ │ │ -  "products": [
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.3" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.3" }
        │ │ │ +  ],
        │ │ │    "status": "fixed"
        │ │ │ -},

        │ │ │ +},

        │ │ │ │ │ │ │ │ │ │ │ │ Third Party VEX Statements │ │ │

        │ │ │

        Erlang/OTP generates statements for third parties from which the project depends │ │ │ on. It is really important to understand the scope of the third party │ │ │ applications, since Erlang/OTP vendors some libraries as part of the runtime.

        Vendoring means that Erlang/OTP code contains a local copy of a library. │ │ │ There are numerous use cases for why this is necessary, and we will not cover the use cases here.

        This excludes dynamically or statically linked libraries during the Erlang/OTP build process. For instance, any security related Erlang application will rely on dynamically or statically linked version of OpenSSL cryptolib.

        Erlang/OTP reports vulnerabilities for any source code that is vulnerable and │ │ │ included in the Erlang/OTP release.

        The OpenVEX statements for our third party libraries specify the affected/fixed │ │ │ version using the commit SHA1 from their respective repository. This is simply │ │ │ because our third party dependencies are in C/C++ and vulnerability scanners │ │ │ such as OSV report vulnerabilities in SHA1 ranges.

        As an example, we mention that the OpenSSL code that Erlang/OTP vendors │ │ │ -is not susceptible for CVE-2023-6129, as follows:

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +is not susceptible for CVE-2023-6129, as follows:

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2023-6129"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-06-18T12:18:16.47247833+02:00",
        │ │ │ -  "products": [
        │ │ │ -     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
        │ │ │ +  ],
        │ │ │    "status": "not_affected",
        │ │ │    "justification": "vulnerable_code_not_present"
        │ │ │ -}

        Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

        In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ +}

        Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

        In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ included with Erlang/OTP). If you build Erlang/OTP and link to any OpenSSL version (e.g., 3.5.2 or even 3.1.4) during the building process, │ │ │ your project has now a new build and runtime dependency and may be subject to CVE-2023-6129.

        │ │ │ │ │ │ │ │ │ │ │ │ Windows Binaries │ │ │

        │ │ ├── ./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-16.2/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 of the form:

            Kernel ! {accept, AcceptorPid, DistController, Family, Proto}

            DistController is either the process or port identifier of the distribution │ │ │ +done by passing a message of 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, Timeout) -> {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, Timeout) -> {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-16.2/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-16.2/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.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -680,15 +680,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.

    │ │ │ │ │ │ │ │ │ @@ -696,41 +696,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 │ │ │ @@ -750,53 +750,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.

    │ │ │ │ │ │ │ │ │ @@ -818,473 +818,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-16.2/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-16.2/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-16.2/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 +S 2 -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 +S 2 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 +S 2 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 │ │ │ @@ -694,15 +694,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>
    │ │ │ @@ -727,30 +727,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 │ │ │ @@ -917,18 +917,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-16.2/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-16.2/doc/html/erl_ext_dist.html │ │ │ @@ -434,15 +434,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-16.2/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-16.2/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)).
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7049,16 +7049,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)).
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8250,19 +8250,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>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8364,19 +8364,19 @@ │ │ │

    Returns an integer or float representing the absolute value of Float │ │ │ or Int.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> abs(-3.33).
    │ │ │ +
    1> abs(-3.33).
    │ │ │  3.33
    │ │ │ -2> abs(-3).
    │ │ │ +2> abs(-3).
    │ │ │  3
    │ │ │ -3> abs(5).
    │ │ │ +3> abs(5).
    │ │ │  5
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8404,16 +8404,16 @@ │ │ │ list_to_tuple(tuple_to_list(Tuple1) ++ [Term]), but │ │ │ faster.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:append_element({one, two}, three).
    │ │ │ -{one,two,three}
    │ │ │ +
    1> erlang:append_element({one, two}, three).
    │ │ │ +{one,two,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8475,19 +8475,19 @@ │ │ │ atom_to_binary(Atom, latin1) may fail if the text │ │ │ representation for Atom contains a Unicode character > 255.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> atom_to_binary('Erlang', latin1).
    │ │ │ -<<"Erlang">>
    │ │ │ -2> atom_to_binary('π', unicode).
    │ │ │ -<<207,128>>
    │ │ │ -3> atom_to_binary('π', latin1).
    │ │ │ +
    1> atom_to_binary('Erlang', latin1).
    │ │ │ +<<"Erlang">>
    │ │ │ +2> atom_to_binary('π', unicode).
    │ │ │ +<<207,128>>
    │ │ │ +3> atom_to_binary('π', latin1).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  atom_to_binary/2
    │ │ │          called as atom_to_binary('π',latin1)
    │ │ │          *** argument 1: contains a character not expressible in latin1
    │ │ │ │ │ │ │ │ │
    │ │ │ @@ -8519,20 +8519,20 @@ │ │ │ of Atom.

    See the unicode module for instructions on converting the resulting list into │ │ │ different formats.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> atom_to_list('Erlang').
    │ │ │ +
    1> atom_to_list('Erlang').
    │ │ │  "Erlang"
    │ │ │ -2> atom_to_list('π').
    │ │ │ -[960]
    │ │ │ -3> atom_to_list('你好').
    │ │ │ -[20320,22909]
    │ │ │ +
    2> atom_to_list('π'). │ │ │ +[960] │ │ │ +3> atom_to_list('你好'). │ │ │ +[20320,22909]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8600,21 +8600,21 @@ │ │ │ outside the binary.

    For details about the semantics of Start and Length, see │ │ │ binary:part/3.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin = <<1,2,3,4,5,6,7,8,9,10>>.
    │ │ │ -2> binary_part(Bin, 0, 2).
    │ │ │ -<<1,2>>
    │ │ │ -3> binary_part(Bin, 2, 3).
    │ │ │ -<<3,4,5>>
    │ │ │ -4> binary_part(Bin, byte_size(Bin), -5).
    │ │ │ -<<6,7,8,9,10>>
    │ │ │ +
    1> Bin = <<1,2,3,4,5,6,7,8,9,10>>.
    │ │ │ +2> binary_part(Bin, 0, 2).
    │ │ │ +<<1,2>>
    │ │ │ +3> binary_part(Bin, 2, 3).
    │ │ │ +<<3,4,5>>
    │ │ │ +4> binary_part(Bin, byte_size(Bin), -5).
    │ │ │ +<<6,7,8,9,10>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_atom(<<"Erlang">>, latin1).
    │ │ │ +
    1> binary_to_atom(<<"Erlang">>, latin1).
    │ │ │  'Erlang'
    │ │ │ -2> binary_to_atom(<<960/utf8>>, utf8).
    │ │ │ +2> binary_to_atom(<<960/utf8>>, utf8).
    │ │ │  'π'
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_existing_atom(~"definitely_not_existing_at_all", utf8).
    │ │ │ +
    1> binary_to_existing_atom(~"definitely_not_existing_at_all", utf8).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  binary_to_existing_atom/2
    │ │ │          called as binary_to_existing_atom(<<"definitely_not_existing_at_all">>,utf8)
    │ │ │          *** argument 1: not an already existing atom
    │ │ │  2> hello.
    │ │ │  hello
    │ │ │ -3> binary_to_existing_atom(~"hello", utf8).
    │ │ │ +3> binary_to_existing_atom(~"hello", utf8).
    │ │ │  hello
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8816,19 +8816,19 @@ │ │ │ Erlang float literals, except that underscores │ │ │ are not permitted.

    Failure: badarg if Binary contains an invalid representation of a float.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_float(~"10.5").
    │ │ │ +
    1> binary_to_float(~"10.5").
    │ │ │  10.5
    │ │ │ -2> binary_to_float(~"17.0").
    │ │ │ +2> binary_to_float(~"17.0").
    │ │ │  17.0
    │ │ │ -3> binary_to_float(<<"2.2017764e+1">>).
    │ │ │ +3> binary_to_float(<<"2.2017764e+1">>).
    │ │ │  22.017764
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8858,19 +8858,19 @@ │ │ │

    Returns an integer whose text representation is Binary.

    binary_to_integer/1 accepts the same string formats │ │ │ as list_to_integer/1.

    Failure: badarg if Binary contains an invalid representation of an integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_integer(<<"123">>).
    │ │ │ +
    1> binary_to_integer(<<"123">>).
    │ │ │  123
    │ │ │ -2> binary_to_integer(<<"-99">>).
    │ │ │ +2> binary_to_integer(<<"-99">>).
    │ │ │  -99
    │ │ │ -3> binary_to_integer(<<"+33">>).
    │ │ │ +3> binary_to_integer(<<"+33">>).
    │ │ │  33
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8899,17 +8899,17 @@ │ │ │ │ │ │

    Returns an integer whose text representation in base Base is Binary.

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

    │ │ │ -
    1> binary_to_integer(<<"3FF">>, 16).
    │ │ │ +
    1> binary_to_integer(<<"3FF">>, 16).
    │ │ │  1023
    │ │ │ -2> binary_to_integer(<<"101">>, 2).
    │ │ │ +2> binary_to_integer(<<"101">>, 2).
    │ │ │  5

    binary_to_integer/2 accepts the same string formats │ │ │ as list_to_integer/2.

    Failure: badarg if Binary contains a invalid representation of an integer.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -8937,16 +8937,16 @@ │ │ │ │ │ │

    Returns a list of integers corresponding to the bytes of Binary.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_list(<<1,2,3>>).
    │ │ │ -[1,2,3]
    │ │ │ +
    1> binary_to_list(<<1,2,3>>).
    │ │ │ +[1,2,3]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8976,15 +8976,15 @@ │ │ │ code should use binary:bin_to_list/3. All functions in │ │ │ module binary consistently use zero-based indexing.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> binary_to_list(~"abcdef", 2, 3).
    │ │ │ +
    1> binary_to_list(~"abcdef", 2, 3).
    │ │ │  "bc"
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin = term_to_binary(hello).
    │ │ │ -<<131,119,5,104,101,108,108,111>>
    │ │ │ -2> hello = binary_to_term(Bin).
    │ │ │ +
    1> Bin = term_to_binary(hello).
    │ │ │ +<<131,119,5,104,101,108,108,111>>
    │ │ │ +2> hello = binary_to_term(Bin).
    │ │ │  hello

    See also term_to_binary/1 and binary_to_term/2.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin = <<131,119,8,"tjenixen">>.
    │ │ │ -2> binary_to_term(Bin, [safe]).
    │ │ │ +
    1> Bin = <<131,119,8,"tjenixen">>.
    │ │ │ +2> binary_to_term(Bin, [safe]).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  binary_to_term/2
    │ │ │          called as binary_to_term(<<131,119,8,116,106,101,110,105,120,101,110>>,[safe])
    │ │ │          *** argument 1: invalid or unsafe external representation of a term
    │ │ │  3> tjenixen.
    │ │ │  tjenixen
    │ │ │ -4> binary_to_term(Bin, [safe]).
    │ │ │ +4> binary_to_term(Bin, [safe]).
    │ │ │  tjenixen
  • used - Changes the return value to {Term, Used} where Used is the │ │ │ number of bytes actually read from Binary.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Input = <<(term_to_binary(hello))/binary, "world">>.
    │ │ │ -<<131,119,5,104,101,108,108,111,119,111,114,108,100>>
    │ │ │ -2> {Term, Used} = binary_to_term(Input, [used]).
    │ │ │ -{hello, 8}
    │ │ │ -3> split_binary(Input, Used).
    │ │ │ -{<<131,119,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.

    │ │ │ +
    1> Input = <<(term_to_binary(hello))/binary, "world">>.
    │ │ │ +<<131,119,5,104,101,108,108,111,119,111,114,108,100>>
    │ │ │ +2> {Term, Used} = binary_to_term(Input, [used]).
    │ │ │ +{hello, 8}
    │ │ │ +3> split_binary(Input, Used).
    │ │ │ +{<<131,119,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.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9128,17 +9128,17 @@ │ │ │ │ │ │

    Returns an integer that is the size in bits of Bitstring.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> bit_size(<<433:16,3:3>>).
    │ │ │ +
    1> bit_size(<<433:16,3:3>>).
    │ │ │  19
    │ │ │ -2> bit_size(<<1,2,3>>).
    │ │ │ +2> bit_size(<<1,2,3>>).
    │ │ │  24
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9166,18 +9166,18 @@ │ │ │

    Returns a list of integers corresponding to the bytes of Bitstring.

    If the number of bits in the binary is not a multiple of 8, the last element of │ │ │ the list is a bitstring containing the remaining 1 to 7 bits.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> bitstring_to_list(<<433:16>>).
    │ │ │ -[1,177]
    │ │ │ -2> bitstring_to_list(<<433:16,3:3>>).
    │ │ │ -[1,177,<<3:3>>]
    │ │ │ +
    1> bitstring_to_list(<<433:16>>).
    │ │ │ +[1,177]
    │ │ │ +2> bitstring_to_list(<<433:16,3:3>>).
    │ │ │ +[1,177,<<3:3>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9206,17 +9206,17 @@ │ │ │

    Returns an integer that is the number of bytes needed to contain Bitstring.

    If the number of bits in Bitstring is not a multiple of 8, the │ │ │ result is rounded up.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> byte_size(<<433:16,3:3>>).
    │ │ │ +
    1> byte_size(<<433:16,3:3>>).
    │ │ │  3
    │ │ │ -2> byte_size(<<1,2,3,4>>).
    │ │ │ +2> byte_size(<<1,2,3,4>>).
    │ │ │  4
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9247,19 +9247,19 @@ │ │ │ │ │ │

    Returns the smallest integer not less than Number.

    See also trunc/1.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> ceil(5.5).
    │ │ │ +
    1> ceil(5.5).
    │ │ │  6
    │ │ │ -2> ceil(-2.3).
    │ │ │ +2> ceil(-2.3).
    │ │ │  -2
    │ │ │ -3> ceil(10.0).
    │ │ │ +3> ceil(10.0).
    │ │ │  10
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9365,18 +9365,18 @@ │ │ │ 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 │ │ │

    │ │ │ -
    1> erlang:decode_packet(1, <<3,"abcd">>, []).
    │ │ │ -{ok,<<"abc">>,<<"d">>}
    │ │ │ -2> erlang:decode_packet(1, <<5,"abcd">>, []).
    │ │ │ -{more,6}
    │ │ │ +
    1> erlang:decode_packet(1, <<3,"abcd">>, []).
    │ │ │ +{ok,<<"abc">>,<<"d">>}
    │ │ │ +2> erlang:decode_packet(1, <<5,"abcd">>, []).
    │ │ │ +{more,6}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9403,16 +9403,16 @@ │ │ │ │ │ │

    Returns a new tuple with element at Index removed from tuple Tuple1.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:delete_element(2, {one, two, three}).
    │ │ │ -{one,three}
    │ │ │ +
    1> erlang:delete_element(2, {one, two, three}).
    │ │ │ +{one,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9468,15 +9468,15 @@ │ │ │ │ │ │

    Returns the Nth element (numbering from 1) of Tuple.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> element(2, {a, b, c}).
    │ │ │ +
    1> element(2, {a, b, c}).
    │ │ │  b
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9498,27 +9498,27 @@ │ │ │ │ │ │ │ │ │

    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

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Term = {ok,"abc"}.
    │ │ │ -2> erlang:external_size(Term).
    │ │ │ +
    1> Term = {ok,"abc"}.
    │ │ │ +2> erlang:external_size(Term).
    │ │ │  13
    │ │ │ -3> byte_size(term_to_binary(Term)).
    │ │ │ +3> byte_size(term_to_binary(Term)).
    │ │ │  13
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    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

    See term_to_binary/2 for a description of the options.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Term = {ok,lists:duplicate(50, $A)}.
    │ │ │ -{ok,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
    │ │ │ -2> erlang:external_size(Term, [compressed]).
    │ │ │ +
    1> Term = {ok,lists:duplicate(50, $A)}.
    │ │ │ +{ok,"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}
    │ │ │ +2> erlang:external_size(Term, [compressed]).
    │ │ │  60
    │ │ │ -3> byte_size(term_to_binary(Term, [compressed])).
    │ │ │ +3> byte_size(term_to_binary(Term, [compressed])).
    │ │ │  26
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9598,15 +9598,15 @@ │ │ │ │ │ │

    Returns a float by converting Number to a float.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> float(55).
    │ │ │ +
    1> 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.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9675,26 +9675,26 @@ │ │ │

    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.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> float_to_binary(7.12, [{decimals, 4}]).
    │ │ │ -<<"7.1200">>
    │ │ │ -2> float_to_binary(7.12, [{decimals, 4}, compact]).
    │ │ │ -<<"7.12">>
    │ │ │ -3> float_to_binary(7.12, [{scientific, 3}]).
    │ │ │ -<<"7.120e+00">>
    │ │ │ -4> float_to_binary(7.12, [short]).
    │ │ │ -<<"7.12">>
    │ │ │ -5> float_to_binary(0.1+0.2, [short]).
    │ │ │ -<<"0.30000000000000004">>
    │ │ │ -6> float_to_binary(0.1+0.2)
    │ │ │ -<<"3.00000000000000044409e-01">>
    │ │ │ +
    1> float_to_binary(7.12, [{decimals, 4}]).
    │ │ │ +<<"7.1200">>
    │ │ │ +2> float_to_binary(7.12, [{decimals, 4}, compact]).
    │ │ │ +<<"7.12">>
    │ │ │ +3> float_to_binary(7.12, [{scientific, 3}]).
    │ │ │ +<<"7.120e+00">>
    │ │ │ +4> float_to_binary(7.12, [short]).
    │ │ │ +<<"7.12">>
    │ │ │ +5> float_to_binary(0.1+0.2, [short]).
    │ │ │ +<<"0.30000000000000004">>
    │ │ │ +6> float_to_binary(0.1+0.2)
    │ │ │ +<<"3.00000000000000044409e-01">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -9768,25 +9768,25 @@ │ │ │ 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 │ │ │

    │ │ │ -
    1> float_to_list(7.12, [{decimals, 4}]).
    │ │ │ +
    1> float_to_list(7.12, [{decimals, 4}]).
    │ │ │  "7.1200"
    │ │ │ -2> float_to_list(7.12, [{decimals, 4}, compact]).
    │ │ │ +2> float_to_list(7.12, [{decimals, 4}, compact]).
    │ │ │  "7.12"
    │ │ │ -3> float_to_list(7.12, [{scientific, 3}]).
    │ │ │ +3> float_to_list(7.12, [{scientific, 3}]).
    │ │ │  "7.120e+00"
    │ │ │ -4> float_to_list(7.12, [short]).
    │ │ │ +4> float_to_list(7.12, [short]).
    │ │ │  "7.12"
    │ │ │ -5> float_to_list(0.1+0.2, [short]).
    │ │ │ +5> float_to_list(0.1+0.2, [short]).
    │ │ │  "0.30000000000000004"
    │ │ │ -6> float_to_list(0.1+0.2)
    │ │ │ +6> 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.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9819,19 +9819,19 @@ │ │ │ │ │ │

    Returns the largest integer not greater than Number.

    See also trunc/1.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> floor(-10.5).
    │ │ │ +
    1> floor(-10.5).
    │ │ │  -11
    │ │ │ -2> floor(5.5).
    │ │ │ +2> floor(5.5).
    │ │ │  5
    │ │ │ -3> floor(10.0).
    │ │ │ +3> floor(10.0).
    │ │ │  10
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -9916,18 +9916,18 @@ │ │ │ new_uniq, uniq, and pid. For an external fun, the value of any of these │ │ │ items is always the atom undefined.

    See erlang:fun_info/1 for a description of the items.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:fun_info(fun() -> ok end, type).
    │ │ │ -{type,local}
    │ │ │ -2> erlang:fun_info(fun lists:sum/1, type).
    │ │ │ -{type,external}
    │ │ │ +
    1> erlang:fun_info(fun() -> ok end, type).
    │ │ │ +{type,local}
    │ │ │ +2> erlang:fun_info(fun lists:sum/1, type).
    │ │ │ +{type,external}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │

    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 │ │ │ +

    -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.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10013,19 +10013,19 @@ │ │ │ │ │ │

    Returns the first element of List.

    It works with improper lists.

    Failure: badarg if List is the empty list [].

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> hd([1,2,3,4,5]).
    │ │ │ +
    1> hd([1,2,3,4,5]).
    │ │ │  1
    │ │ │ -2> hd([first, second, third, so_on | improper_end]).
    │ │ │ +2> hd([first, second, third, so_on | improper_end]).
    │ │ │  first
    │ │ │ -3> hd([]).
    │ │ │ +3> hd([]).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  hd/1
    │ │ │          called as hd([])
    │ │ │          *** argument 1: not a nonempty list
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10059,16 +10059,16 @@ │ │ │ Tuple1.

    All elements from position Index and upwards are pushed one step │ │ │ higher in the new tuple Tuple2.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:insert_element(2, {one, two, three}, new).
    │ │ │ -{one,new,two,three}
    │ │ │ +
    1> erlang:insert_element(2, {one, two, three}, new).
    │ │ │ +{one,new,two,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10096,16 +10096,16 @@ │ │ │ │ │ │

    Returns a binary corresponding to the text representation of Integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_binary(77).
    │ │ │ -<<"77">>
    │ │ │ +
    1> integer_to_binary(77).
    │ │ │ +<<"77">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10134,16 +10134,16 @@ │ │ │

    Returns a binary corresponding to the text representation of Integer in base │ │ │ Base.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_binary(1023, 16).
    │ │ │ -<<"3FF">>
    │ │ │ +
    1> integer_to_binary(1023, 16).
    │ │ │ +<<"3FF">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10169,15 +10169,15 @@ │ │ │ │ │ │

    Returns a string corresponding to the text representation of Integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_list(77).
    │ │ │ +
    1> integer_to_list(77).
    │ │ │  "77"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10205,15 +10205,15 @@ │ │ │

    Returns a string corresponding to the text representation of Integer in base │ │ │ Base.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_list(1023, 16).
    │ │ │ +
    1> integer_to_list(1023, 16).
    │ │ │  "3FF"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10241,15 +10241,15 @@ │ │ │

    Returns the size in bytes of the binary that would result from │ │ │ iolist_to_binary(Item).

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> iolist_size([1,2|<<3,4>>]).
    │ │ │ +
    1> iolist_size([1,2|<<3,4>>]).
    │ │ │  4
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10277,22 +10277,22 @@ │ │ │

    Returns a binary constructed from the integers and binaries in │ │ │ IoListOrBinary.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -4> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │ +
    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +4> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10324,28 +10324,28 @@ │ │ │ efficient message passing. The advantage of using this function over │ │ │ iolist_to_binary/1 is that it does not need to copy off-heap binaries.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -

    If you pass small binaries and integers, it works like iolist_to_binary/1.

    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -4> 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,...>>]
    │ │ │ +

    If you pass small binaries and integers, it works like iolist_to_binary/1.

    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +4> 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,...>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10373,17 +10373,17 @@ │ │ │ │ │ │

    Returns true if Term is an atom; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_atom(42).
    │ │ │ +
    1> is_atom(42).
    │ │ │  false
    │ │ │ -2> is_atom(ok).
    │ │ │ +2> is_atom(ok).
    │ │ │  true
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10412,19 +10412,19 @@ │ │ │ │ │ │

    Returns true if Term is a binary; otherwise, returns false.

    A binary always contains a complete number of bytes.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_binary(42).
    │ │ │ +
    1> is_binary(42).
    │ │ │  false
    │ │ │ -2> is_binary(<<1,2,3>>).
    │ │ │ +2> is_binary(<<1,2,3>>).
    │ │ │  true
    │ │ │ -3> is_binary(<<7:12>>).
    │ │ │ +3> is_binary(<<7:12>>).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10453,19 +10453,19 @@ │ │ │ │ │ │

    Returns true if Term is a bitstring (including a binary); otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_bitstring(42).
    │ │ │ +
    1> is_bitstring(42).
    │ │ │  false
    │ │ │ -2> is_bitstring(<<1,2,3>>).
    │ │ │ +2> is_bitstring(<<1,2,3>>).
    │ │ │  true
    │ │ │ -3> is_bitstring(<<7:12>>).
    │ │ │ +3> is_bitstring(<<7:12>>).
    │ │ │  true
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10494,21 +10494,21 @@ │ │ │ │ │ │

    Returns true if Term is the atom true or false; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_boolean(true).
    │ │ │ +
    1> is_boolean(true).
    │ │ │  true
    │ │ │ -2> is_boolean(false).
    │ │ │ +2> is_boolean(false).
    │ │ │  true
    │ │ │ -3> is_boolean(ok).
    │ │ │ +3> is_boolean(ok).
    │ │ │  false
    │ │ │ -4> is_boolean(42).
    │ │ │ +4> is_boolean(42).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10537,19 +10537,19 @@ │ │ │ │ │ │

    Returns true if Term is a floating point number; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_float(42).
    │ │ │ +
    1> is_float(42).
    │ │ │  false
    │ │ │ -2> is_float(42.0).
    │ │ │ +2> is_float(42.0).
    │ │ │  true
    │ │ │ -3> is_float(zero).
    │ │ │ +3> is_float(zero).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10578,19 +10578,19 @@ │ │ │ │ │ │

    Returns true if Term is a fun; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_function(fun() -> ok end).
    │ │ │ +
    1> is_function(fun() -> ok end).
    │ │ │  true
    │ │ │ -2> is_function(fun lists:sum/1).
    │ │ │ +2> is_function(fun lists:sum/1).
    │ │ │  true
    │ │ │ -3> is_function({lists,sum}).
    │ │ │ +3> is_function({lists,sum}).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10620,26 +10620,26 @@ │ │ │

    Returns true if Term is a fun that can be applied with Arity number of │ │ │ arguments; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_function(fun() -> ok end, 0).
    │ │ │ +
    1> is_function(fun() -> ok end, 0).
    │ │ │  true
    │ │ │ -2> is_function(fun lists:sum/1, 1).
    │ │ │ +2> is_function(fun lists:sum/1, 1).
    │ │ │  true
    │ │ │ -3> is_function({lists,sum}, 1).
    │ │ │ +3> is_function({lists,sum}, 1).
    │ │ │  false
    │ │ │ -4> is_function(fun lists:sum/1, -1).
    │ │ │ +4> is_function(fun lists:sum/1, -1).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  is_function/2
    │ │ │          called as is_function(fun lists:sum/1,-1)
    │ │ │          *** argument 2: out of range
    │ │ │ -5> is_function(fun lists:sum/1, bad_arity).
    │ │ │ +5> is_function(fun lists:sum/1, bad_arity).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  is_function/2
    │ │ │          called as is_function(fun lists:sum/1,bad_arity)
    │ │ │          *** argument 2: not an integer
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10671,21 +10671,21 @@ │ │ │ │ │ │

    Returns true if Term is an integer; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_integer(1).
    │ │ │ +
    1> is_integer(1).
    │ │ │  true
    │ │ │ -2> is_integer(-1234567890123456789012345678901234567890).
    │ │ │ +2> is_integer(-1234567890123456789012345678901234567890).
    │ │ │  true
    │ │ │ -3> is_integer(1.0).
    │ │ │ +3> is_integer(1.0).
    │ │ │  false
    │ │ │ -4> is_integer(zero).
    │ │ │ +4> is_integer(zero).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10715,23 +10715,23 @@ │ │ │

    Returns true if Term is a list with zero or more elements; otherwise, │ │ │ returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_list({a,b,c}).
    │ │ │ +
    1> is_list({a,b,c}).
    │ │ │  false
    │ │ │ -2> is_list([]).
    │ │ │ +2> is_list([]).
    │ │ │  true
    │ │ │ -3> is_list([1]).
    │ │ │ +3> is_list([1]).
    │ │ │  true
    │ │ │ -4> is_list([1,2]).
    │ │ │ +4> is_list([1,2]).
    │ │ │  true
    │ │ │ -5> is_list([1,2|3]).
    │ │ │ +5> is_list([1,2|3]).
    │ │ │  true
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10762,19 +10762,19 @@ │ │ │ │ │ │

    Returns true if Term is a map; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_map(#{}).
    │ │ │ +
    1> is_map(#{}).
    │ │ │  true
    │ │ │ -2> is_map(#{key => value}).
    │ │ │ +2> is_map(#{key => value}).
    │ │ │  true
    │ │ │ -3> is_map([]).
    │ │ │ +3> is_map([]).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10806,21 +10806,21 @@ │ │ │

    Returns true if map Map contains Key and returns false if it does not │ │ │ contain the Key.

    Failure: A {badmap,Map} exception is raised if Map is not a map.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    > Map = #{"42" => value}.
    │ │ │ -#{"42" => value}
    │ │ │ -1> is_map_key("42", Map).
    │ │ │ +
    > Map = #{"42" => value}.
    │ │ │ +#{"42" => value}
    │ │ │ +1> is_map_key("42", Map).
    │ │ │  true
    │ │ │ -2> is_map_key(value, Map).
    │ │ │ +2> is_map_key(value, Map).
    │ │ │  false
    │ │ │ -3> is_map_key(value, no_map).
    │ │ │ +3> is_map_key(value, no_map).
    │ │ │  ** exception error: bad map: no_map
    │ │ │       in function  is_map_key/2
    │ │ │          called as is_map_key(value,no_map)
    │ │ │          *** argument 2: not a map
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -10853,19 +10853,19 @@ │ │ │

    Returns true if Term is an integer or a floating point number; otherwise, │ │ │ returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_number(10.0).
    │ │ │ +
    1> is_number(10.0).
    │ │ │  true
    │ │ │ -2> is_number(7).
    │ │ │ +2> is_number(7).
    │ │ │  true
    │ │ │ -3> is_number(zero).
    │ │ │ +3> is_number(zero).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10894,17 +10894,17 @@ │ │ │ │ │ │

    Returns true if Term is a process identifier; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_pid(self()).
    │ │ │ +
    1> is_pid(self()).
    │ │ │  true
    │ │ │ -2> is_pid(ok).
    │ │ │ +2> is_pid(ok).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10933,18 +10933,18 @@ │ │ │ │ │ │

    Returns true if Term is a port identifier; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> APort = hd(erlang:ports()).
    │ │ │ -2> is_port(APort).
    │ │ │ +
    1> APort = hd(erlang:ports()).
    │ │ │ +2> is_port(APort).
    │ │ │  true
    │ │ │ -3> is_port(self()).
    │ │ │ +3> is_port(self()).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11042,17 +11042,17 @@ │ │ │ │ │ │

    Returns true if Term is a reference; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_reference(make_ref()).
    │ │ │ +
    1> is_reference(make_ref()).
    │ │ │  true
    │ │ │ -2> is_reference(self()).
    │ │ │ +2> is_reference(self()).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11081,17 +11081,17 @@ │ │ │ │ │ │

    Returns true if Term is a tuple; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_tuple({a, b, c}).
    │ │ │ +
    1> is_tuple({a, b, c}).
    │ │ │  true
    │ │ │ -2> is_tuple([a, b, c]).
    │ │ │ +2> is_tuple([a, b, c]).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11120,17 +11120,17 @@ │ │ │ │ │ │

    Returns the length of List.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> length([1,2,3,4,5,6,7,8,9]).
    │ │ │ +
    1> length([1,2,3,4,5,6,7,8,9]).
    │ │ │  9
    │ │ │ -2> length([a,b|c]).
    │ │ │ +2> length([a,b|c]).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  length/1
    │ │ │          called as length([a,b|c])
    │ │ │          *** argument 1: not a list
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -11170,17 +11170,17 @@ │ │ │ than list_to_atom/1.

    The number of characters that are permitted in an atom name is │ │ │ limited.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_atom("Erlang").
    │ │ │ +
    1> list_to_atom("Erlang").
    │ │ │  'Erlang'
    │ │ │ -2> list_to_atom([960]).
    │ │ │ +2> list_to_atom([960]).
    │ │ │  'π'
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11207,22 +11207,22 @@ │ │ │ │ │ │

    Returns a binary made from the integers and binaries in IoList.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -4> list_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │ +
    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +4> list_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11249,22 +11249,22 @@ │ │ │

    Returns a bitstring made from the integers and bitstrings in │ │ │ BitstringList.

    The last tail in BitstringList is allowed to be a bitstring.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6,7:4>>.
    │ │ │ -<<6,7:4>>
    │ │ │ -4> list_to_bitstring([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6,7:4>>
    │ │ │ +
    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6,7:4>>.
    │ │ │ +<<6,7:4>>
    │ │ │ +4> list_to_bitstring([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6,7:4>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_existing_atom("a_blatal_DOS_attack").
    │ │ │ +
    1> list_to_existing_atom("a_blatal_DOS_attack").
    │ │ │  ** exception error: bad argument
    │ │ │       in function  list_to_existing_atom/1
    │ │ │          called as list_to_existing_atom("a_blatal_DOS_attack")
    │ │ │          *** argument 1: not an already existing atom
    │ │ │  2> hello.
    │ │ │  hello
    │ │ │ -3> list_to_existing_atom("hello").
    │ │ │ +3> list_to_existing_atom("hello").
    │ │ │  hello
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11340,15 +11340,15 @@ │ │ │ │ │ │

    Returns the float whose text representation is String.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_float("2.2017764e+0").
    │ │ │ +
    1> 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 invalid representation of a float.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11379,19 +11379,19 @@ │ │ │ prefix consisting of a single "+" or "-" character (that is, String must │ │ │ match the regular expression "^[+-]?[0-9]+$").

    Failure: badarg if String contains a invalid representation of an integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_integer("123").
    │ │ │ +
    1> list_to_integer("123").
    │ │ │  123
    │ │ │ -2> list_to_integer("-123").
    │ │ │ +2> list_to_integer("-123").
    │ │ │  -123
    │ │ │ -3> list_to_integer("+123234982304982309482093833234234").
    │ │ │ +3> list_to_integer("+123234982304982309482093833234234").
    │ │ │  123234982304982309482093833234234
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11419,25 +11419,25 @@ │ │ │

    Returns an integer whose text representation in base Base is String.

    String must contain at least one digit character and can have an optional │ │ │ prefix consisting of a single "+" or "-" character.

    Failure: badarg if String contains an invalid integer representation.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_integer("3FF", 16).
    │ │ │ +
    1> list_to_integer("3FF", 16).
    │ │ │  1023
    │ │ │ -2> list_to_integer("+3FF", 16).
    │ │ │ +2> list_to_integer("+3FF", 16).
    │ │ │  1023
    │ │ │ -3> list_to_integer("3ff", 16).
    │ │ │ +3> list_to_integer("3ff", 16).
    │ │ │  1023
    │ │ │ -4> list_to_integer("-3FF", 16).
    │ │ │ +4> list_to_integer("-3FF", 16).
    │ │ │  -1023
    │ │ │ -5> list_to_integer("Base36IsFun", 36).
    │ │ │ +5> list_to_integer("Base36IsFun", 36).
    │ │ │  41313437507787071
    │ │ │ -6> list_to_integer("102", 2).
    │ │ │ +6> list_to_integer("102", 2).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  list_to_integer/2
    │ │ │          called as list_to_integer("102",2)
    │ │ │          *** argument 1: not a textual representation of an integer
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -11469,15 +11469,15 @@ │ │ │ identifier.

    Warning

    This BIF is intended for debugging and is not to be used in application │ │ │ programs.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    > list_to_pid("<0.4.1>").
    │ │ │ +
    > list_to_pid("<0.4.1>").
    │ │ │  <0.4.1>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11508,15 +11508,15 @@ │ │ │ identifier.

    Warning

    This BIF is intended for debugging and is not to be used in application │ │ │ programs.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    > list_to_port("#Port<0.4>").
    │ │ │ +
    > list_to_port("#Port<0.4>").
    │ │ │  #Port<0.4>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11546,15 +11546,15 @@ │ │ │

    Returns a reference whose text representation is a String.

    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.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    > list_to_ref("#Ref<0.4192537678.4073193475.71181>").
    │ │ │ +
    > list_to_ref("#Ref<0.4192537678.4073193475.71181>").
    │ │ │  #Ref<0.4192537678.4073193475.71181>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11581,16 +11581,16 @@ │ │ │ │ │ │

    Returns a tuple whose elements are the elements of List.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> list_to_tuple([share, ['Ericsson_B', 163]]).
    │ │ │ -{share, ['Ericsson_B', 163]}
    │ │ │ +
    1> list_to_tuple([share, ['Ericsson_B', 163]]).
    │ │ │ +{share, ['Ericsson_B', 163]}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_reference(make_ref()).
    │ │ │ +
    1> is_reference(make_ref()).
    │ │ │  true
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11652,16 +11652,16 @@ │ │ │

    Creates a new tuple of the specified Arity, where all elements are │ │ │ InitialValue.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:make_tuple(4, []).
    │ │ │ -{[],[],[],[]}
    │ │ │ +
    1> erlang:make_tuple(4, []).
    │ │ │ +{[],[],[],[]}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11693,16 +11693,16 @@ │ │ │ position occurs more than once in the list, the term corresponding to the last │ │ │ occurrence is used.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:make_tuple(5, [], [{2,ignored},{5,zz},{2,aa}]).
    │ │ │ -{[],aa,[],[],zz}
    │ │ │ +
    1> erlang:make_tuple(5, [], [{2,ignored},{5,zz},{2,aa}]).
    │ │ │ +{[],aa,[],[],zz}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11734,23 +11734,23 @@ │ │ │ {badkey,Key} exception if no value is associated with Key.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │
    1> Key = 1337.
    │ │ │ -2> Map = #{42 => value_two,Key => "value one","a" => 1}.
    │ │ │ -3> map_get(Key, Map).
    │ │ │ +2> Map = #{42 => value_two,Key => "value one","a" => 1}.
    │ │ │ +3> map_get(Key, Map).
    │ │ │  "value one"
    │ │ │ -4> map_get(unknown_key, Map).
    │ │ │ +4> map_get(unknown_key, Map).
    │ │ │  ** exception error: bad key: unknown_key
    │ │ │       in function  map_get/2
    │ │ │          called as map_get(unknown_key,#{42 => value_two,1337 => "value one","a" => 1})
    │ │ │          *** argument 1: not present in map
    │ │ │ -5> map_get(key, no_map).
    │ │ │ +5> map_get(key, no_map).
    │ │ │  ** exception error: bad map: no_map
    │ │ │       in function  map_get/2
    │ │ │          called as map_get(key,no_map)
    │ │ │          *** argument 2: not a map
    │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -11784,15 +11784,15 @@ │ │ │ │ │ │

    Returns the number of key-value pairs in Map.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> map_size(#{a=>1, b=>2, c=>3}).
    │ │ │ +
    1> map_size(#{a=>1, b=>2, c=>3}).
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11836,19 +11836,19 @@ │ │ │ returns {ok, Result, Flags, Warnings}, where Result is one of the following:

    • true if a trace message is to be emitted
    • false if a trace message is not to be emitted
    • The message term to be appended to the trace message

    Flags is a list of trace flags to be enabled; currently, the only │ │ │ available flag is return_trace.

    See also ets:test_ms/2.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Ms = [{{'$1','$2'}, [], [{{'$2','$1'}}]}].
    │ │ │ -2> erlang:match_spec_test({a,b}, Ms, table).
    │ │ │ -{ok,{b,a},[],[]}
    │ │ │ -3> erlang:match_spec_test({a,b,c}, Ms, table).
    │ │ │ -{ok,false,[],[]}
    │ │ │ +
    1> Ms = [{{'$1','$2'}, [], [{{'$2','$1'}}]}].
    │ │ │ +2> erlang:match_spec_test({a,b}, Ms, table).
    │ │ │ +{ok,{b,a},[],[]}
    │ │ │ +3> erlang:match_spec_test({a,b,c}, Ms, table).
    │ │ │ +{ok,false,[],[]}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11877,21 +11877,21 @@ │ │ │

    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.

    Change

    Allowed in guards tests from Erlang/OTP 26.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples: │ │ │

    │ │ │ -
    1> max(1, 2).
    │ │ │ +
    1> max(1, 2).
    │ │ │  2
    │ │ │ -2> max(1.0, 1).
    │ │ │ +2> max(1.0, 1).
    │ │ │  1.0
    │ │ │ -3> max(1, 1.0).
    │ │ │ +3> max(1, 1.0).
    │ │ │  1
    │ │ │ -4> max("abc", "b").
    │ │ │ +4> max("abc", "b").
    │ │ │  "b"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11921,21 +11921,21 @@ │ │ │

    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.

    Change

    Allowed in guards tests from Erlang/OTP 26.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    > min(1, 2).
    │ │ │ +
    > min(1, 2).
    │ │ │  1
    │ │ │ -> min(1.0, 1).
    │ │ │ +> min(1.0, 1).
    │ │ │  1.0
    │ │ │ -> min(1, 1.0).
    │ │ │ +> min(1, 1.0).
    │ │ │  1
    │ │ │ -> min("abc", "b").
    │ │ │ +> min("abc", "b").
    │ │ │  "abc"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -12030,21 +12030,21 @@ │ │ │ large integers and binaries.

    Notice that the range 0..Range-1 is different from the range of │ │ │ phash/2, which is 1..Range.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:phash2({a,b,c}, 1_000).
    │ │ │ +
    1> erlang:phash2({a,b,c}, 1_000).
    │ │ │  870
    │ │ │ -2> erlang:phash2(41, 1_000).
    │ │ │ +2> erlang:phash2(41, 1_000).
    │ │ │  297
    │ │ │ -3> erlang:phash2(42, 1_000).
    │ │ │ +3> erlang:phash2(42, 1_000).
    │ │ │  368
    │ │ │ -4> erlang:phash2(43, 1_000).
    │ │ │ +4> erlang:phash2(43, 1_000).
    │ │ │  725
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │