--- /srv/rebuilderd/tmp/rebuilderdpULp7J/inputs/erlang-doc_29.0~rc3+dfsg-1_all.deb +++ /srv/rebuilderd/tmp/rebuilderdpULp7J/out/erlang-doc_29.0~rc3+dfsg-1_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2026-04-15 18:59:02.000000 debian-binary │ --rw-r--r-- 0 0 0 61052 2026-04-15 18:59:02.000000 control.tar.xz │ --rw-r--r-- 0 0 0 24936576 2026-04-15 18:59:02.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 61056 2026-04-15 18:59:02.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 24936212 2026-04-15 18:59:02.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -814,15 +814,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/search_data-4CDEAB42.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/search_data-311DD30B.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/sidebar_items-2D7BEBEB.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp_client.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/notes.html │ │ │ │ @@ -1244,15 +1244,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/search_data-62376163.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/search_data-A2F66A60.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/sidebar_items-C4781606.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_architecture.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_meas.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_mstone1.html │ │ │ │ @@ -1581,15 +1581,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/search_data-D13A7ABE.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/search_data-AA86FF31.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/sidebar_items-77A3D458.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dtrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dyntrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/instrument.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/lttng.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/msacc.html │ │ │ │ @@ -1770,15 +1770,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/search_data-118FFFFC.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/search_data-E0CE4C2F.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/sidebar_items-93509C6F.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/hardening.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh.epub │ │ │ │ @@ -1875,15 +1875,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/search_data-7E77D3EA.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/search_data-16FF2648.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/sidebar_items-F80C2F06.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/edlin.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/edlin_expand.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/epp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_anno.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_error.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_eval.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -141,15 +141,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 287 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 288 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 290 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 296 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2392 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5648 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 805598 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 805612 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53684 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 97565 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -411,15 +411,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11077 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 4963 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/assets/config.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 10726 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/assets/html_logs.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 9561 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/assets/tc_execution.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 21789 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/basics_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 411864 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/common_test.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 411848 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/common_test.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7496 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/common_test_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59620 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/config_file_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25535 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/cover_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 182313 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/ct.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12266 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/ct_cover.html │ │ │ -rw-r--r-- 0 root (0) root (0) 48059 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/ct_doctest.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33479 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/ct_ftp.html │ │ │ @@ -481,15 +481,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 24217 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/beam_ssa.html │ │ │ -rw-r--r-- 0 root (0) root (0) 472387 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/cerl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28678 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/cerl_clauses.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28900 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/cerl_trees.html │ │ │ -rw-r--r-- 0 root (0) root (0) 92536 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/compile.html │ │ │ --rw-r--r-- 0 root (0) root (0) 203383 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/compiler.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 203380 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/compiler.epub │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ -rw-r--r-- 0 root (0) root (0) 17732 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/inconsolata-latin-400-normal-OXLHDACS.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 17976 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/inconsolata-latin-700-normal-S55P5GAG.woff2 │ │ │ @@ -515,15 +515,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 978 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 36793 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/algorithm_details.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6662 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 140721 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 140720 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 357300 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11222 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto_app.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -560,15 +560,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 21770 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/cond_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 13532 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/function_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 28924 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/interpret.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 14414 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/line_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 40742 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/monitor.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 34504 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/assets/view.jpg │ │ │ --rw-r--r-- 0 root (0) root (0) 222108 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/debugger.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 222104 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/debugger.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13075 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/debugger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52058 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/debugger_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-7.0/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -597,15 +597,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 921 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6786 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 69052 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dialyzer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 69057 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dialyzer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53565 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dialyzer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25892 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dialyzer_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -650,15 +650,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 2379 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/examples/dict/rfc4740_sip.dia.gz │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1143 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8206 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 149019 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 149015 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 253460 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57499 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 27922 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_codec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34940 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_dict.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6772 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9512 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23265 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_make.html │ │ │ @@ -784,15 +784,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 26583 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/search_data-E0177CFA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 6179 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/dist/sidebar_items-14671279.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33489 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/eldap.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33486 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/eldap.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 94377 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/eldap.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28399 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5920 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ │ │ │ @@ -821,15 +821,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 199647 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/dist/search_data-1374700A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 16288 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/dist/sidebar_items-5DE9DFF9.js │ │ │ -rw-r--r-- 0 root (0) root (0) 73718 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ei.html │ │ │ -rw-r--r-- 0 root (0) root (0) 72769 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ei_connect.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11729 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ei_global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26962 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ei_users_guide.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22654 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/erl_call_cmd.html │ │ │ --rw-r--r-- 0 root (0) root (0) 86613 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/erl_interface.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 86617 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/erl_interface.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 270 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 119876 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5559 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/examples/ │ │ │ -rw-r--r-- 0 root (0) root (0) 15431 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/examples/et_demo.erl │ │ │ @@ -868,15 +868,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 83801 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/search_data-12B7FA19.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9353 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/dist/sidebar_items-952B1C2C.js │ │ │ --rw-r--r-- 0 root (0) root (0) 303465 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 303477 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 22745 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et.html │ │ │ -rw-r--r-- 0 root (0) root (0) 56797 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_collector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52810 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_desc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100660 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9902 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20249 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_selector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45772 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_tutorial.html │ │ │ @@ -912,15 +912,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 85206 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/search_data-A1AE6A4D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3265 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/sidebar_items-F9DCA965.js │ │ │ --rw-r--r-- 0 root (0) root (0) 49115 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/eunit.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 49116 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/eunit.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13555 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/eunit.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6630 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/eunit_surefire.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50523 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/ │ │ │ @@ -944,15 +944,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 30562 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/search_data-4CDEAB42.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 30562 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/search_data-311DD30B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5312 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/dist/sidebar_items-2D7BEBEB.js │ │ │ -rw-r--r-- 0 root (0) root (0) 36914 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 100195 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12856 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/ftp_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7162 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24184 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.5/doc/html/notes.html │ │ │ @@ -1117,15 +1117,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11377 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/http_uri.html │ │ │ -rw-r--r-- 0 root (0) root (0) 92071 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 119541 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12076 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpd_custom_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13391 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpd_socket.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44888 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpd_util.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 161058 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/inets.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 161056 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/inets.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 25609 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/inets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8647 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/inets_services.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7454 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21176 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/mod_alias.html │ │ │ -rw-r--r-- 0 root (0) root (0) 82372 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/mod_auth.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21842 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/mod_esi.html │ │ │ -rw-r--r-- 0 root (0) root (0) 36914 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/mod_security.html │ │ │ @@ -1363,15 +1363,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57052 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37162 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/global_group.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24939 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/heart.html │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 183978 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/inet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 94509 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/inet_res.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7721 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/introduction_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 847813 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/kernel.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 847820 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/kernel.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 43555 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/kernel_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 187933 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110886 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70805 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger_cookbook.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15633 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger_disk_log_h.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25604 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger_filters.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34063 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/logger_formatter.html │ │ │ @@ -1428,15 +1428,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 213955 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/search_data-62376163.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 213955 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/search_data-A2F66A60.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33651 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/sidebar_items-C4781606.js │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 147235 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 198731 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17696 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_architecture.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9834 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_meas.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26192 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_mstone1.html │ │ │ @@ -1511,15 +1511,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 388686 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/search_data-2D33460F.js │ │ │ -rw-r--r-- 0 root (0) root (0) 25859 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/sidebar_items-B6E0F975.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 227766 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 227776 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 342526 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45462 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_a.html │ │ │ -rw-r--r-- 0 root (0) root (0) 90426 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_b.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46054 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_c.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9863 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_chap1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109242 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_chap2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51388 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_chap3.html │ │ │ @@ -1567,15 +1567,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 150971 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/dist/search_data-1305868C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12965 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/dist/sidebar_items-58F82598.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17922 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/etop.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15742 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/etop_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7350 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/introduction_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77604 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 118781 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 118783 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13881 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7238 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23444 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5941 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111527 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/ttb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 165958 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/ttb_ug.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/ │ │ │ @@ -1609,15 +1609,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 80050 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/dist/search_data-8D6ABF37.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7442 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/dist/sidebar_items-322DE651.js │ │ │ -rw-r--r-- 0 root (0) root (0) 13859 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/error_handling.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51373 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8466 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62027 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 70076 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/odbc.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 70074 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/odbc.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 87515 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/odbc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6013 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/404.html │ │ │ @@ -1681,15 +1681,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 59376 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/dist/search_data-F5FAE48C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5788 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/dist/sidebar_items-4228A8DE.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 55377 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/leex.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45293 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 46411 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 46404 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/parsetools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 5950 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67783 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/yecc.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6037 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/404.html │ │ │ @@ -1714,15 +1714,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 159405 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/search_data-6689052C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17735 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/sidebar_items-3F271350.js │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106876 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 106038 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/public_key.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 106035 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/public_key.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 214724 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/public_key.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10613 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/public_key_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 74413 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/public_key_records.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5953 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 131319 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/using_public_key.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/ │ │ │ @@ -1753,15 +1753,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 94042 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/search_data-E3ED7B7B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 8980 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/sidebar_items-B51BD5DF.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51499 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 64542 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 64547 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 100508 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool.html │ │ │ -rw-r--r-- 0 root (0) root (0) 200344 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9362 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23132 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool_usage.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5932 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/ │ │ │ @@ -1810,24 +1810,24 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 195211 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/search_data-D13A7ABE.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 195211 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/search_data-AA86FF31.js │ │ │ -rw-r--r-- 0 root (0) root (0) 18805 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dist/sidebar_items-77A3D458.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9768 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dtrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47608 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/dyntrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 269 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50748 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/instrument.html │ │ │ -rw-r--r-- 0 root (0) root (0) 64733 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/lttng.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49987 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/msacc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 90165 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 136500 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/runtime_tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 136493 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/runtime_tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7578 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/runtime_tools_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29007 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/scheduler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5968 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12883 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/system_information.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10216 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/systemtap.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/ │ │ │ @@ -1868,15 +1868,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 34632 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/error_logging.html │ │ │ -rw-r--r-- 0 root (0) root (0) 260 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77807 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 42342 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/rb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12206 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/rel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 80220 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/release_handler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9567 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/relup.html │ │ │ --rw-r--r-- 0 root (0) root (0) 94197 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/sasl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 94192 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/sasl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 17143 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/sasl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7693 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/sasl_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17239 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/script.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 40261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/systools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/ │ │ │ @@ -1937,15 +1937,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 559580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/dist/search_data-B3816D8C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 89974 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/dist/sidebar_items-A1C37470.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 74993 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 451466 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 451500 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 147483 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39983 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_advanced_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62877 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_agent_config_files.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51341 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_agent_funct_descr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18119 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_agent_netif.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66597 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8609 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.3/doc/html/snmp_app_a.html │ │ │ @@ -2020,22 +2020,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 408275 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/search_data-118FFFFC.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 408275 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/search_data-E0CE4C2F.js │ │ │ -rw-r--r-- 0 root (0) root (0) 48336 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/dist/sidebar_items-93509C6F.js │ │ │ -rw-r--r-- 0 root (0) root (0) 52039 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/hardening.html │ │ │ -rw-r--r-- 0 root (0) root (0) 259 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14405 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 269599 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 259259 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 259248 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 266133 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24735 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25836 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44535 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_client_channel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23183 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_client_key_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78519 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_connection.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50564 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-6.0/doc/html/ssh_file.html │ │ │ @@ -2092,15 +2092,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 510294 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/dist/search_data-976A26A2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 28303 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/dist/sidebar_items-2CDF1A40.js │ │ │ -rw-r--r-- 0 root (0) root (0) 260 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 287679 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5911 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 220346 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 220338 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 331440 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17335 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12854 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_crl_cache.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21688 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_crl_cache_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29623 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_distribution.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14198 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25827 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.6/doc/html/ssl_session_cache_api.html │ │ │ @@ -2145,15 +2145,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 2209626 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/search_data-7E77D3EA.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 2209626 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/search_data-16FF2648.js │ │ │ -rw-r--r-- 0 root (0) root (0) 276640 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/dist/sidebar_items-F80C2F06.js │ │ │ -rw-r--r-- 0 root (0) root (0) 18671 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/edlin.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15220 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/edlin_expand.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52673 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/epp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 42699 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_anno.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33744 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_error.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57418 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/erl_eval.html │ │ │ @@ -2207,15 +2207,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110942 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/sets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110236 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/shell.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10133 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/shell_default.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49860 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/shell_docs.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33169 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/slave.html │ │ │ -rw-r--r-- 0 root (0) root (0) 503109 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/sofs.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1743015 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/stdlib.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1742975 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/stdlib.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 16124 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/stdlib_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 191936 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/string.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100472 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/supervisor.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20517 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/supervisor_bridge.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106837 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/sys.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34820 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/terminal_interface.html │ │ │ -rw-r--r-- 0 root (0) root (0) 81120 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/timer.html │ │ │ @@ -2299,15 +2299,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 31548 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/dist/search_data-4456E8EA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3329 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/dist/sidebar_items-65ADE05C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 10545 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 260 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8806 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23156 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 31995 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/tftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 31994 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/tftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 45783 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/tftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11721 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/tftp_logger.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/examples/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1447 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/examples/xref_examples.erl │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/ │ │ │ @@ -2350,15 +2350,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66036 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/lcnt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 53457 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/lcnt_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18377 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 116266 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5920 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28511 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 242436 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 242451 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 173622 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 192203 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/xref.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39571 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/xref_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/examples/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/examples/demo/ │ │ │ @@ -2451,15 +2451,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1672717 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/dist/search_data-A7576480.js │ │ │ -rw-r--r-- 0 root (0) root (0) 578947 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/dist/sidebar_items-631B9BA1.js │ │ │ -rw-r--r-- 0 root (0) root (0) 1720063 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/gl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77248 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/glu.html │ │ │ -rw-r--r-- 0 root (0) root (0) 258 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 69962 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5902 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1609738 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wx.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1609740 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wx.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53710 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wx.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19399 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxAcceleratorEntry.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15114 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxAcceleratorTable.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12357 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxActivateEvent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19080 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxArtProvider.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17602 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxAuiDockArt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62907 2026-04-15 18:59:02.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wxAuiManager.html │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ ├── zipinfo {} │ │ │ │ @@ -1,99 +1,99 @@ │ │ │ │ -Zip file size: 805598 bytes, number of entries: 97 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 26-Apr-15 20:07 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 14643 bx defN 26-Apr-15 20:07 OEBPS/vulnerabilities.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 17925 bx defN 26-Apr-15 20:07 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4673 bx defN 26-Apr-15 20:07 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 54465 bx defN 26-Apr-15 20:07 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2166 bx defN 26-Apr-15 20:07 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 764 bx defN 26-Apr-15 20:07 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46257 bx defN 26-Apr-15 20:07 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12439 bx defN 26-Apr-15 20:07 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7481 bx defN 26-Apr-15 20:07 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63519 bx defN 26-Apr-15 20:07 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 254168 bx defN 26-Apr-15 20:07 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111649 bx defN 26-Apr-15 20:07 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252881 bx defN 26-Apr-15 20:07 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 163061 bx defN 26-Apr-15 20:07 OEBPS/secure_coding.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15505 bx defN 26-Apr-15 20:07 OEBPS/sbom.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70567 bx defN 26-Apr-15 20:07 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20877 bx defN 26-Apr-15 20:07 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 59979 bx defN 26-Apr-15 20:07 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4595 bx defN 26-Apr-15 20:07 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19454 bx defN 26-Apr-15 20:07 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 54417 bx defN 26-Apr-15 20:07 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14474 bx defN 26-Apr-15 20:07 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49208 bx defN 26-Apr-15 20:07 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2357 bx defN 26-Apr-15 20:07 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 785 bx defN 26-Apr-15 20:07 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40219 bx defN 26-Apr-15 20:07 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15192 bx defN 26-Apr-15 20:07 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8499 bx defN 26-Apr-15 20:07 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3852 bx defN 26-Apr-15 20:07 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13419 bx defN 26-Apr-15 20:07 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8959 bx defN 26-Apr-15 20:07 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 10050 bx defN 26-Apr-15 20:07 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15633 bx defN 26-Apr-15 20:07 OEBPS/nominals.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14148 bx defN 26-Apr-15 20:07 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6852 bx defN 26-Apr-15 20:07 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 25870 bx defN 26-Apr-15 20:07 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7018 bx defN 26-Apr-15 20:07 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5477 bx defN 26-Apr-15 20:07 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53379 bx defN 26-Apr-15 20:07 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39594 bx defN 26-Apr-15 20:07 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31440 bx defN 26-Apr-15 20:07 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53789 bx defN 26-Apr-15 20:07 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2211 bx defN 26-Apr-15 20:07 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 57455 bx defN 26-Apr-15 20:07 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28229 bx defN 26-Apr-15 20:07 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35715 bx defN 26-Apr-15 20:07 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21439 bx defN 26-Apr-15 20:07 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3222 bx defN 26-Apr-15 20:07 OEBPS/howto_debug.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2353 bx defN 26-Apr-15 20:07 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31366 bx defN 26-Apr-15 20:07 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 119139 bx defN 26-Apr-15 20:07 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8680 bx defN 26-Apr-15 20:07 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 268117 bx defN 26-Apr-15 20:07 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2365 bx defN 26-Apr-15 20:07 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26770 bx defN 26-Apr-15 20:07 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16719 bx defN 26-Apr-15 20:07 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4519 bx defN 26-Apr-15 20:07 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 42616 bx defN 26-Apr-15 20:07 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18220 bx defN 26-Apr-15 20:07 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2085 bx defN 26-Apr-15 20:07 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46685 bx defN 26-Apr-15 20:07 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21210 bx defN 26-Apr-15 20:07 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9338 bx defN 26-Apr-15 20:07 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47780 bx defN 26-Apr-15 20:07 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14895 bx defN 26-Apr-15 20:07 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24447 bx defN 26-Apr-15 20:07 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14562 bx defN 26-Apr-15 20:07 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 26-Apr-15 20:07 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36776 bx defN 26-Apr-15 20:07 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15003 bx defN 26-Apr-15 20:07 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 78764 bx defN 26-Apr-15 20:07 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 116619 bx defN 26-Apr-15 20:07 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14028 bx defN 26-Apr-15 20:07 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 129434 bx defN 26-Apr-15 20:07 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35528 bx defN 26-Apr-15 20:07 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11940 bx defN 26-Apr-15 20:07 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 803 bx defN 26-Apr-15 20:07 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5151 bx defN 26-Apr-15 20:07 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40799 bx defN 26-Apr-15 20:07 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35602 bx defN 26-Apr-15 20:07 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34851 bx defN 26-Apr-15 20:07 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53327 bx defN 26-Apr-15 20:07 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7596 bx defN 26-Apr-15 20:07 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 88568 bx defN 26-Apr-15 20:07 OEBPS/assets/prio-msg-recv.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Apr-15 20:07 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Apr-15 20:07 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 26-Apr-15 20:07 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 26-Apr-15 20:07 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 26-Apr-15 20:07 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 26-Apr-15 20:07 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 26-Apr-15 20:07 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 26-Apr-15 20:07 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 91824 bx defN 26-Apr-15 20:07 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47930 bx defN 26-Apr-15 20:07 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 26-Apr-15 20:07 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 26-Apr-15 20:07 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -97 files, 3429456 bytes uncompressed, 788616 bytes compressed: 77.0% │ │ │ │ +Zip file size: 805612 bytes, number of entries: 97 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 26-Apr-21 04:13 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 14643 bx defN 26-Apr-21 04:13 OEBPS/vulnerabilities.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 17925 bx defN 26-Apr-21 04:13 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4673 bx defN 26-Apr-21 04:13 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 54465 bx defN 26-Apr-21 04:13 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2166 bx defN 26-Apr-21 04:13 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 764 bx defN 26-Apr-21 04:13 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46257 bx defN 26-Apr-21 04:13 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12439 bx defN 26-Apr-21 04:13 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7481 bx defN 26-Apr-21 04:13 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63519 bx defN 26-Apr-21 04:13 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 254168 bx defN 26-Apr-21 04:13 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111649 bx defN 26-Apr-21 04:13 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252881 bx defN 26-Apr-21 04:13 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 163061 bx defN 26-Apr-21 04:13 OEBPS/secure_coding.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15505 bx defN 26-Apr-21 04:13 OEBPS/sbom.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70567 bx defN 26-Apr-21 04:13 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20877 bx defN 26-Apr-21 04:13 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 59979 bx defN 26-Apr-21 04:13 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4595 bx defN 26-Apr-21 04:13 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19454 bx defN 26-Apr-21 04:13 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 54417 bx defN 26-Apr-21 04:13 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14474 bx defN 26-Apr-21 04:13 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49208 bx defN 26-Apr-21 04:13 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2357 bx defN 26-Apr-21 04:13 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 785 bx defN 26-Apr-21 04:13 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40219 bx defN 26-Apr-21 04:13 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15192 bx defN 26-Apr-21 04:13 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8499 bx defN 26-Apr-21 04:13 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3852 bx defN 26-Apr-21 04:13 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13419 bx defN 26-Apr-21 04:13 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8959 bx defN 26-Apr-21 04:13 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 10050 bx defN 26-Apr-21 04:13 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15633 bx defN 26-Apr-21 04:13 OEBPS/nominals.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14148 bx defN 26-Apr-21 04:13 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6852 bx defN 26-Apr-21 04:13 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 25870 bx defN 26-Apr-21 04:13 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7018 bx defN 26-Apr-21 04:13 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5477 bx defN 26-Apr-21 04:13 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53379 bx defN 26-Apr-21 04:13 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39594 bx defN 26-Apr-21 04:13 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31440 bx defN 26-Apr-21 04:13 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53789 bx defN 26-Apr-21 04:13 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2211 bx defN 26-Apr-21 04:13 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 57455 bx defN 26-Apr-21 04:13 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28229 bx defN 26-Apr-21 04:13 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35715 bx defN 26-Apr-21 04:13 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21439 bx defN 26-Apr-21 04:13 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3222 bx defN 26-Apr-21 04:13 OEBPS/howto_debug.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2353 bx defN 26-Apr-21 04:13 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31366 bx defN 26-Apr-21 04:13 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 119139 bx defN 26-Apr-21 04:13 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8680 bx defN 26-Apr-21 04:13 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 268117 bx defN 26-Apr-21 04:13 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2365 bx defN 26-Apr-21 04:13 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26770 bx defN 26-Apr-21 04:13 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16719 bx defN 26-Apr-21 04:13 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4519 bx defN 26-Apr-21 04:13 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 42616 bx defN 26-Apr-21 04:13 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18220 bx defN 26-Apr-21 04:13 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2085 bx defN 26-Apr-21 04:13 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46685 bx defN 26-Apr-21 04:13 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21210 bx defN 26-Apr-21 04:13 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9338 bx defN 26-Apr-21 04:13 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47780 bx defN 26-Apr-21 04:13 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14895 bx defN 26-Apr-21 04:13 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24447 bx defN 26-Apr-21 04:13 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14562 bx defN 26-Apr-21 04:13 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 26-Apr-21 04:13 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36776 bx defN 26-Apr-21 04:13 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15003 bx defN 26-Apr-21 04:13 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 78764 bx defN 26-Apr-21 04:13 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 116619 bx defN 26-Apr-21 04:13 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14028 bx defN 26-Apr-21 04:13 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 129434 bx defN 26-Apr-21 04:13 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35528 bx defN 26-Apr-21 04:13 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11940 bx defN 26-Apr-21 04:13 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 803 bx defN 26-Apr-21 04:13 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5151 bx defN 26-Apr-21 04:13 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40799 bx defN 26-Apr-21 04:13 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35602 bx defN 26-Apr-21 04:13 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34851 bx defN 26-Apr-21 04:13 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53327 bx defN 26-Apr-21 04:13 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7596 bx defN 26-Apr-21 04:13 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 88568 bx defN 26-Apr-21 04:13 OEBPS/assets/prio-msg-recv.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Apr-21 04:13 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Apr-21 04:13 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 26-Apr-21 04:13 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 26-Apr-21 04:13 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 26-Apr-21 04:13 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 26-Apr-21 04:13 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 26-Apr-21 04:13 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 26-Apr-21 04:13 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 91824 bx defN 26-Apr-21 04:13 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47930 bx defN 26-Apr-21 04:13 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 26-Apr-21 04:13 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 26-Apr-21 04:13 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +97 files, 3429456 bytes uncompressed, 788630 bytes compressed: 77.0% │ │ │ ├── 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 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ +0000A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 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 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -0002F Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ +0002B Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +0002F Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 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,6663 +31,6663 @@ │ │ │ │ │ │ │ │ 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 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -00064 CRC 29F05690 (703616656) │ │ │ │ -00068 Compressed Size 00000D24 (3364) │ │ │ │ +00060 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +00064 CRC 53A58720 (1403356960) │ │ │ │ +00068 Compressed Size 00000D22 (3362) │ │ │ │ 0006C Uncompressed Size 00003933 (14643) │ │ │ │ 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 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -00098 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ +00094 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +00098 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 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 │ │ │ │ │ │ │ │ -00DCF LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ -00DD3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -00DD4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -00DD5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -00DD7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -00DD9 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -00DDD CRC 546A35FD (1416246781) │ │ │ │ -00DE1 Compressed Size 000015B1 (5553) │ │ │ │ -00DE5 Uncompressed Size 00004605 (17925) │ │ │ │ -00DE9 Filename Length 0014 (20) │ │ │ │ -00DEB Extra Length 001C (28) │ │ │ │ -00DED Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xDED: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -00E01 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -00E03 Length 0009 (9) │ │ │ │ -00E05 Flags 03 (3) 'Modification Access' │ │ │ │ -00E06 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -00E0A Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -00E0E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -00E10 Length 000B (11) │ │ │ │ -00E12 Version 01 (1) │ │ │ │ -00E13 UID Size 04 (4) │ │ │ │ -00E14 UID 00000000 (0) │ │ │ │ -00E18 GID Size 04 (4) │ │ │ │ -00E19 GID 00000000 (0) │ │ │ │ -00E1D 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 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -023DC CRC 03A4A46E (61121646) │ │ │ │ -023E0 Compressed Size 000006D4 (1748) │ │ │ │ -023E4 Uncompressed Size 00001241 (4673) │ │ │ │ -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 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -02408 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 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 │ │ │ │ - │ │ │ │ -02AEF LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -02AF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -02AF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -02AF5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -02AF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -02AF9 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -02AFD CRC 404477C9 (1078228937) │ │ │ │ -02B01 Compressed Size 00002E75 (11893) │ │ │ │ -02B05 Uncompressed Size 0000D4C1 (54465) │ │ │ │ -02B09 Filename Length 0014 (20) │ │ │ │ -02B0B Extra Length 001C (28) │ │ │ │ -02B0D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B0D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -02B21 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -02B23 Length 0009 (9) │ │ │ │ -02B25 Flags 03 (3) 'Modification Access' │ │ │ │ -02B26 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -02B2A Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -02B2E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -02B30 Length 000B (11) │ │ │ │ -02B32 Version 01 (1) │ │ │ │ -02B33 UID Size 04 (4) │ │ │ │ -02B34 UID 00000000 (0) │ │ │ │ -02B38 GID Size 04 (4) │ │ │ │ -02B39 GID 00000000 (0) │ │ │ │ -02B3D PAYLOAD │ │ │ │ - │ │ │ │ -059B2 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ -059B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -059B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -059B8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -059BA Compression Method 0008 (8) 'Deflated' │ │ │ │ -059BC Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -059C0 CRC 272DD323 (657314595) │ │ │ │ -059C4 Compressed Size 000003EF (1007) │ │ │ │ -059C8 Uncompressed Size 00000876 (2166) │ │ │ │ -059CC Filename Length 0014 (20) │ │ │ │ -059CE Extra Length 001C (28) │ │ │ │ -059D0 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x59D0: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -059E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -059E6 Length 0009 (9) │ │ │ │ -059E8 Flags 03 (3) 'Modification Access' │ │ │ │ -059E9 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -059ED Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -059F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -059F3 Length 000B (11) │ │ │ │ -059F5 Version 01 (1) │ │ │ │ -059F6 UID Size 04 (4) │ │ │ │ -059F7 UID 00000000 (0) │ │ │ │ -059FB GID Size 04 (4) │ │ │ │ -059FC GID 00000000 (0) │ │ │ │ -05A00 PAYLOAD │ │ │ │ - │ │ │ │ -05DEF LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ -05DF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -05DF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -05DF5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -05DF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -05DF9 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -05DFD CRC 2F07766F (789018223) │ │ │ │ -05E01 Compressed Size 000001AD (429) │ │ │ │ -05E05 Uncompressed Size 000002FC (764) │ │ │ │ -05E09 Filename Length 0011 (17) │ │ │ │ -05E0B Extra Length 001C (28) │ │ │ │ -05E0D Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5E0D: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -05E1E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -05E20 Length 0009 (9) │ │ │ │ -05E22 Flags 03 (3) 'Modification Access' │ │ │ │ -05E23 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -05E27 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -05E2B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -05E2D Length 000B (11) │ │ │ │ -05E2F Version 01 (1) │ │ │ │ -05E30 UID Size 04 (4) │ │ │ │ -05E31 UID 00000000 (0) │ │ │ │ -05E35 GID Size 04 (4) │ │ │ │ -05E36 GID 00000000 (0) │ │ │ │ -05E3A PAYLOAD │ │ │ │ - │ │ │ │ -05FE7 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ -05FEB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -05FEC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -05FED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -05FEF Compression Method 0008 (8) 'Deflated' │ │ │ │ -05FF1 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -05FF5 CRC AD55D2AE (2908082862) │ │ │ │ -05FF9 Compressed Size 000020BE (8382) │ │ │ │ -05FFD Uncompressed Size 0000B4B1 (46257) │ │ │ │ -06001 Filename Length 001B (27) │ │ │ │ -06003 Extra Length 001C (28) │ │ │ │ -06005 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6005: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -06020 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -06022 Length 0009 (9) │ │ │ │ -06024 Flags 03 (3) 'Modification Access' │ │ │ │ -06025 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -06029 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -0602D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0602F Length 000B (11) │ │ │ │ -06031 Version 01 (1) │ │ │ │ -06032 UID Size 04 (4) │ │ │ │ -06033 UID 00000000 (0) │ │ │ │ -06037 GID Size 04 (4) │ │ │ │ -06038 GID 00000000 (0) │ │ │ │ -0603C PAYLOAD │ │ │ │ - │ │ │ │ -080FA LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -080FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -080FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08100 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08102 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08104 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -08108 CRC 4E72FA06 (1316157958) │ │ │ │ -0810C Compressed Size 00000E68 (3688) │ │ │ │ -08110 Uncompressed Size 00003097 (12439) │ │ │ │ -08114 Filename Length 001D (29) │ │ │ │ -08116 Extra Length 001C (28) │ │ │ │ -08118 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8118: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08135 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08137 Length 0009 (9) │ │ │ │ -08139 Flags 03 (3) 'Modification Access' │ │ │ │ -0813A Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -0813E Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -08142 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08144 Length 000B (11) │ │ │ │ -08146 Version 01 (1) │ │ │ │ -08147 UID Size 04 (4) │ │ │ │ -08148 UID 00000000 (0) │ │ │ │ -0814C GID Size 04 (4) │ │ │ │ -0814D GID 00000000 (0) │ │ │ │ -08151 PAYLOAD │ │ │ │ - │ │ │ │ -08FB9 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08FBD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08FBE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08FBF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08FC1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08FC3 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -08FC7 CRC F138337A (4046992250) │ │ │ │ -08FCB Compressed Size 0000098E (2446) │ │ │ │ -08FCF Uncompressed Size 00001D39 (7481) │ │ │ │ -08FD3 Filename Length 0019 (25) │ │ │ │ -08FD5 Extra Length 001C (28) │ │ │ │ -08FD7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8FD7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08FF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08FF2 Length 0009 (9) │ │ │ │ -08FF4 Flags 03 (3) 'Modification Access' │ │ │ │ -08FF5 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -08FF9 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -08FFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08FFF Length 000B (11) │ │ │ │ -09001 Version 01 (1) │ │ │ │ -09002 UID Size 04 (4) │ │ │ │ -09003 UID 00000000 (0) │ │ │ │ -09007 GID Size 04 (4) │ │ │ │ -09008 GID 00000000 (0) │ │ │ │ -0900C PAYLOAD │ │ │ │ - │ │ │ │ -0999A LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0999E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0999F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -099A0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -099A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -099A4 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -099A8 CRC FC4F496F (4233054575) │ │ │ │ -099AC Compressed Size 00003883 (14467) │ │ │ │ -099B0 Uncompressed Size 0000F81F (63519) │ │ │ │ -099B4 Filename Length 0015 (21) │ │ │ │ -099B6 Extra Length 001C (28) │ │ │ │ -099B8 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x99B8: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -099CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -099CF Length 0009 (9) │ │ │ │ -099D1 Flags 03 (3) 'Modification Access' │ │ │ │ -099D2 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -099D6 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -099DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -099DC Length 000B (11) │ │ │ │ -099DE Version 01 (1) │ │ │ │ -099DF UID Size 04 (4) │ │ │ │ -099E0 UID 00000000 (0) │ │ │ │ -099E4 GID Size 04 (4) │ │ │ │ -099E5 GID 00000000 (0) │ │ │ │ -099E9 PAYLOAD │ │ │ │ - │ │ │ │ -0D26C LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -0D270 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0D271 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0D272 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0D274 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0D276 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -0D27A CRC 0BB598F2 (196450546) │ │ │ │ -0D27E Compressed Size 0000AAFA (43770) │ │ │ │ -0D282 Uncompressed Size 0003E0D8 (254168) │ │ │ │ -0D286 Filename Length 0012 (18) │ │ │ │ -0D288 Extra Length 001C (28) │ │ │ │ -0D28A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xD28A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0D29C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0D29E Length 0009 (9) │ │ │ │ -0D2A0 Flags 03 (3) 'Modification Access' │ │ │ │ -0D2A1 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -0D2A5 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -0D2A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0D2AB Length 000B (11) │ │ │ │ -0D2AD Version 01 (1) │ │ │ │ -0D2AE UID Size 04 (4) │ │ │ │ -0D2AF UID 00000000 (0) │ │ │ │ -0D2B3 GID Size 04 (4) │ │ │ │ -0D2B4 GID 00000000 (0) │ │ │ │ -0D2B8 PAYLOAD │ │ │ │ - │ │ │ │ -17DB2 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ -17DB6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -17DB7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -17DB8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -17DBA Compression Method 0008 (8) 'Deflated' │ │ │ │ -17DBC Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -17DC0 CRC C50A848B (3305800843) │ │ │ │ -17DC4 Compressed Size 00003AF8 (15096) │ │ │ │ -17DC8 Uncompressed Size 0001B421 (111649) │ │ │ │ -17DCC Filename Length 0015 (21) │ │ │ │ -17DCE Extra Length 001C (28) │ │ │ │ -17DD0 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x17DD0: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -17DE5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -17DE7 Length 0009 (9) │ │ │ │ -17DE9 Flags 03 (3) 'Modification Access' │ │ │ │ -17DEA Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -17DEE Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -17DF2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -17DF4 Length 000B (11) │ │ │ │ -17DF6 Version 01 (1) │ │ │ │ -17DF7 UID Size 04 (4) │ │ │ │ -17DF8 UID 00000000 (0) │ │ │ │ -17DFC GID Size 04 (4) │ │ │ │ -17DFD GID 00000000 (0) │ │ │ │ -17E01 PAYLOAD │ │ │ │ - │ │ │ │ -1B8F9 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -1B8FD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -1B8FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -1B8FF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -1B901 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1B903 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -1B907 CRC B6C2DA9C (3066223260) │ │ │ │ -1B90B Compressed Size 000091C5 (37317) │ │ │ │ -1B90F Uncompressed Size 0003DBD1 (252881) │ │ │ │ -1B913 Filename Length 0014 (20) │ │ │ │ -1B915 Extra Length 001C (28) │ │ │ │ -1B917 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x1B917: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -1B92B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -1B92D Length 0009 (9) │ │ │ │ -1B92F Flags 03 (3) 'Modification Access' │ │ │ │ -1B930 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -1B934 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -1B938 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -1B93A Length 000B (11) │ │ │ │ -1B93C Version 01 (1) │ │ │ │ -1B93D UID Size 04 (4) │ │ │ │ -1B93E UID 00000000 (0) │ │ │ │ -1B942 GID Size 04 (4) │ │ │ │ -1B943 GID 00000000 (0) │ │ │ │ -1B947 PAYLOAD │ │ │ │ - │ │ │ │ -24B0C LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -24B10 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -24B11 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -24B12 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -24B14 Compression Method 0008 (8) 'Deflated' │ │ │ │ -24B16 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -24B1A CRC 6E4D0A9B (1850542747) │ │ │ │ -24B1E Compressed Size 00009BA8 (39848) │ │ │ │ -24B22 Uncompressed Size 00027CF5 (163061) │ │ │ │ -24B26 Filename Length 0019 (25) │ │ │ │ -24B28 Extra Length 001C (28) │ │ │ │ -24B2A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x24B2A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -24B43 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -24B45 Length 0009 (9) │ │ │ │ -24B47 Flags 03 (3) 'Modification Access' │ │ │ │ -24B48 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -24B4C Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -24B50 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -24B52 Length 000B (11) │ │ │ │ -24B54 Version 01 (1) │ │ │ │ -24B55 UID Size 04 (4) │ │ │ │ -24B56 UID 00000000 (0) │ │ │ │ -24B5A GID Size 04 (4) │ │ │ │ -24B5B GID 00000000 (0) │ │ │ │ -24B5F PAYLOAD │ │ │ │ - │ │ │ │ -2E707 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -2E70B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2E70C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2E70D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2E70F Compression Method 0008 (8) 'Deflated' │ │ │ │ -2E711 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -2E715 CRC 36D4AE72 (919907954) │ │ │ │ -2E719 Compressed Size 00001219 (4633) │ │ │ │ -2E71D Uncompressed Size 00003C91 (15505) │ │ │ │ -2E721 Filename Length 0010 (16) │ │ │ │ -2E723 Extra Length 001C (28) │ │ │ │ -2E725 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2E725: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2E735 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2E737 Length 0009 (9) │ │ │ │ -2E739 Flags 03 (3) 'Modification Access' │ │ │ │ -2E73A Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -2E73E Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -2E742 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2E744 Length 000B (11) │ │ │ │ -2E746 Version 01 (1) │ │ │ │ -2E747 UID Size 04 (4) │ │ │ │ -2E748 UID 00000000 (0) │ │ │ │ -2E74C GID Size 04 (4) │ │ │ │ -2E74D GID 00000000 (0) │ │ │ │ -2E751 PAYLOAD │ │ │ │ - │ │ │ │ -2F96A LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -2F96E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2F96F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2F970 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2F972 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2F974 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -2F978 CRC 0BA4F9CD (195361229) │ │ │ │ -2F97C Compressed Size 00002A5F (10847) │ │ │ │ -2F980 Uncompressed Size 000113A7 (70567) │ │ │ │ -2F984 Filename Length 0016 (22) │ │ │ │ -2F986 Extra Length 001C (28) │ │ │ │ -2F988 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2F988: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2F99E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2F9A0 Length 0009 (9) │ │ │ │ -2F9A2 Flags 03 (3) 'Modification Access' │ │ │ │ -2F9A3 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -2F9A7 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -2F9AB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2F9AD Length 000B (11) │ │ │ │ -2F9AF Version 01 (1) │ │ │ │ -2F9B0 UID Size 04 (4) │ │ │ │ -2F9B1 UID 00000000 (0) │ │ │ │ -2F9B5 GID Size 04 (4) │ │ │ │ -2F9B6 GID 00000000 (0) │ │ │ │ -2F9BA PAYLOAD │ │ │ │ - │ │ │ │ -32419 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ -3241D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3241E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3241F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32421 Compression Method 0008 (8) 'Deflated' │ │ │ │ -32423 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -32427 CRC AF0A8C6F (2936704111) │ │ │ │ -3242B Compressed Size 000014D9 (5337) │ │ │ │ -3242F Uncompressed Size 0000518D (20877) │ │ │ │ -32433 Filename Length 001D (29) │ │ │ │ -32435 Extra Length 001C (28) │ │ │ │ -32437 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32437: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32454 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32456 Length 0009 (9) │ │ │ │ -32458 Flags 03 (3) 'Modification Access' │ │ │ │ -32459 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3245D Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -32461 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32463 Length 000B (11) │ │ │ │ -32465 Version 01 (1) │ │ │ │ -32466 UID Size 04 (4) │ │ │ │ -32467 UID 00000000 (0) │ │ │ │ -3246B GID Size 04 (4) │ │ │ │ -3246C GID 00000000 (0) │ │ │ │ -32470 PAYLOAD │ │ │ │ - │ │ │ │ -33949 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -3394D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3394E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3394F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -33951 Compression Method 0008 (8) 'Deflated' │ │ │ │ -33953 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -33957 CRC 0B59709A (190410906) │ │ │ │ -3395B Compressed Size 000037FF (14335) │ │ │ │ -3395F Uncompressed Size 0000EA4B (59979) │ │ │ │ -33963 Filename Length 001C (28) │ │ │ │ -33965 Extra Length 001C (28) │ │ │ │ -33967 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33967: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -33983 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33985 Length 0009 (9) │ │ │ │ -33987 Flags 03 (3) 'Modification Access' │ │ │ │ -33988 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3398C Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -33990 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -33992 Length 000B (11) │ │ │ │ -33994 Version 01 (1) │ │ │ │ -33995 UID Size 04 (4) │ │ │ │ -33996 UID 00000000 (0) │ │ │ │ -3399A GID Size 04 (4) │ │ │ │ -3399B GID 00000000 (0) │ │ │ │ -3399F PAYLOAD │ │ │ │ - │ │ │ │ -3719E LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -371A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -371A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -371A4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -371A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -371A8 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -371AC CRC 958EC234 (2509161012) │ │ │ │ -371B0 Compressed Size 0000069E (1694) │ │ │ │ -371B4 Uncompressed Size 000011F3 (4595) │ │ │ │ -371B8 Filename Length 001C (28) │ │ │ │ -371BA Extra Length 001C (28) │ │ │ │ -371BC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x371BC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -371D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -371DA Length 0009 (9) │ │ │ │ -371DC Flags 03 (3) 'Modification Access' │ │ │ │ -371DD Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -371E1 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -371E5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -371E7 Length 000B (11) │ │ │ │ -371E9 Version 01 (1) │ │ │ │ -371EA UID Size 04 (4) │ │ │ │ -371EB UID 00000000 (0) │ │ │ │ -371EF GID Size 04 (4) │ │ │ │ -371F0 GID 00000000 (0) │ │ │ │ -371F4 PAYLOAD │ │ │ │ - │ │ │ │ -37892 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -37896 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -37897 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -37898 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3789A Compression Method 0008 (8) 'Deflated' │ │ │ │ -3789C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -378A0 CRC 42501F7E (1112547198) │ │ │ │ -378A4 Compressed Size 0000107D (4221) │ │ │ │ -378A8 Uncompressed Size 00004BFE (19454) │ │ │ │ -378AC Filename Length 001B (27) │ │ │ │ -378AE Extra Length 001C (28) │ │ │ │ -378B0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x378B0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -378CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -378CD Length 0009 (9) │ │ │ │ -378CF Flags 03 (3) 'Modification Access' │ │ │ │ -378D0 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -378D4 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -378D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -378DA Length 000B (11) │ │ │ │ -378DC Version 01 (1) │ │ │ │ -378DD UID Size 04 (4) │ │ │ │ -378DE UID 00000000 (0) │ │ │ │ -378E2 GID Size 04 (4) │ │ │ │ -378E3 GID 00000000 (0) │ │ │ │ -378E7 PAYLOAD │ │ │ │ - │ │ │ │ -38964 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -38968 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38969 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3896A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3896C Compression Method 0008 (8) 'Deflated' │ │ │ │ -3896E Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -38972 CRC 3D4A57FE (1028282366) │ │ │ │ -38976 Compressed Size 00003B3B (15163) │ │ │ │ -3897A Uncompressed Size 0000D491 (54417) │ │ │ │ -3897E Filename Length 001D (29) │ │ │ │ -38980 Extra Length 001C (28) │ │ │ │ -38982 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x38982: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3899F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -389A1 Length 0009 (9) │ │ │ │ -389A3 Flags 03 (3) 'Modification Access' │ │ │ │ -389A4 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -389A8 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -389AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -389AE Length 000B (11) │ │ │ │ -389B0 Version 01 (1) │ │ │ │ -389B1 UID Size 04 (4) │ │ │ │ -389B2 UID 00000000 (0) │ │ │ │ -389B6 GID Size 04 (4) │ │ │ │ -389B7 GID 00000000 (0) │ │ │ │ -389BB PAYLOAD │ │ │ │ - │ │ │ │ -3C4F6 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -3C4FA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3C4FB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3C4FC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3C4FE Compression Method 0008 (8) 'Deflated' │ │ │ │ -3C500 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -3C504 CRC 76464E51 (1984319057) │ │ │ │ -3C508 Compressed Size 00000D6B (3435) │ │ │ │ -3C50C Uncompressed Size 0000388A (14474) │ │ │ │ -3C510 Filename Length 001D (29) │ │ │ │ -3C512 Extra Length 001C (28) │ │ │ │ -3C514 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C514: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C531 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C533 Length 0009 (9) │ │ │ │ -3C535 Flags 03 (3) 'Modification Access' │ │ │ │ -3C536 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3C53A Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3C53E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C540 Length 000B (11) │ │ │ │ -3C542 Version 01 (1) │ │ │ │ -3C543 UID Size 04 (4) │ │ │ │ -3C544 UID 00000000 (0) │ │ │ │ -3C548 GID Size 04 (4) │ │ │ │ -3C549 GID 00000000 (0) │ │ │ │ -3C54D PAYLOAD │ │ │ │ - │ │ │ │ -3D2B8 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -3D2BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3D2BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3D2BE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3D2C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3D2C2 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -3D2C6 CRC 50EF047D (1357841533) │ │ │ │ -3D2CA Compressed Size 00001C86 (7302) │ │ │ │ -3D2CE Uncompressed Size 0000C038 (49208) │ │ │ │ -3D2D2 Filename Length 001A (26) │ │ │ │ -3D2D4 Extra Length 001C (28) │ │ │ │ -3D2D6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3D2D6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3D2F0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3D2F2 Length 0009 (9) │ │ │ │ -3D2F4 Flags 03 (3) 'Modification Access' │ │ │ │ -3D2F5 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3D2F9 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3D2FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3D2FF Length 000B (11) │ │ │ │ -3D301 Version 01 (1) │ │ │ │ -3D302 UID Size 04 (4) │ │ │ │ -3D303 UID 00000000 (0) │ │ │ │ -3D307 GID Size 04 (4) │ │ │ │ -3D308 GID 00000000 (0) │ │ │ │ -3D30C PAYLOAD │ │ │ │ - │ │ │ │ -3EF92 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -3EF96 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3EF97 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3EF98 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3EF9A Compression Method 0008 (8) 'Deflated' │ │ │ │ -3EF9C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -3EFA0 CRC 478B6B96 (1200319382) │ │ │ │ -3EFA4 Compressed Size 000003DF (991) │ │ │ │ -3EFA8 Uncompressed Size 00000935 (2357) │ │ │ │ -3EFAC Filename Length 0012 (18) │ │ │ │ -3EFAE Extra Length 001C (28) │ │ │ │ -3EFB0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3EFB0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3EFC2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3EFC4 Length 0009 (9) │ │ │ │ -3EFC6 Flags 03 (3) 'Modification Access' │ │ │ │ -3EFC7 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3EFCB Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3EFCF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3EFD1 Length 000B (11) │ │ │ │ -3EFD3 Version 01 (1) │ │ │ │ -3EFD4 UID Size 04 (4) │ │ │ │ -3EFD5 UID 00000000 (0) │ │ │ │ -3EFD9 GID Size 04 (4) │ │ │ │ -3EFDA GID 00000000 (0) │ │ │ │ -3EFDE PAYLOAD │ │ │ │ - │ │ │ │ -3F3BD LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -3F3C1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3F3C2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3F3C3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3F3C5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3F3C7 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -3F3CB CRC E79489F0 (3885271536) │ │ │ │ -3F3CF Compressed Size 000001D3 (467) │ │ │ │ -3F3D3 Uncompressed Size 00000311 (785) │ │ │ │ -3F3D7 Filename Length 0020 (32) │ │ │ │ -3F3D9 Extra Length 001C (28) │ │ │ │ -3F3DB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3F3DB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3F3FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3F3FD Length 0009 (9) │ │ │ │ -3F3FF Flags 03 (3) 'Modification Access' │ │ │ │ -3F400 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3F404 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3F408 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3F40A Length 000B (11) │ │ │ │ -3F40C Version 01 (1) │ │ │ │ -3F40D UID Size 04 (4) │ │ │ │ -3F40E UID 00000000 (0) │ │ │ │ -3F412 GID Size 04 (4) │ │ │ │ -3F413 GID 00000000 (0) │ │ │ │ -3F417 PAYLOAD │ │ │ │ - │ │ │ │ -3F5EA LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -3F5EE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3F5EF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3F5F0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3F5F2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3F5F4 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -3F5F8 CRC BB37392E (3140958510) │ │ │ │ -3F5FC Compressed Size 000017AE (6062) │ │ │ │ -3F600 Uncompressed Size 00009D1B (40219) │ │ │ │ -3F604 Filename Length 001B (27) │ │ │ │ -3F606 Extra Length 001C (28) │ │ │ │ -3F608 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3F608: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3F623 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3F625 Length 0009 (9) │ │ │ │ -3F627 Flags 03 (3) 'Modification Access' │ │ │ │ -3F628 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3F62C Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -3F630 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3F632 Length 000B (11) │ │ │ │ -3F634 Version 01 (1) │ │ │ │ -3F635 UID Size 04 (4) │ │ │ │ -3F636 UID 00000000 (0) │ │ │ │ -3F63A GID Size 04 (4) │ │ │ │ -3F63B GID 00000000 (0) │ │ │ │ -3F63F PAYLOAD │ │ │ │ - │ │ │ │ -40DED LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -40DF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -40DF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -40DF3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -40DF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -40DF7 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -40DFB CRC 09EB9A96 (166435478) │ │ │ │ -40DFF Compressed Size 0000136D (4973) │ │ │ │ -40E03 Uncompressed Size 00003B58 (15192) │ │ │ │ -40E07 Filename Length 0015 (21) │ │ │ │ -40E09 Extra Length 001C (28) │ │ │ │ -40E0B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x40E0B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40E20 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40E22 Length 0009 (9) │ │ │ │ -40E24 Flags 03 (3) 'Modification Access' │ │ │ │ -40E25 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -40E29 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -40E2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40E2F Length 000B (11) │ │ │ │ -40E31 Version 01 (1) │ │ │ │ -40E32 UID Size 04 (4) │ │ │ │ -40E33 UID 00000000 (0) │ │ │ │ -40E37 GID Size 04 (4) │ │ │ │ -40E38 GID 00000000 (0) │ │ │ │ -40E3C PAYLOAD │ │ │ │ - │ │ │ │ -421A9 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -421AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -421AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -421AF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -421B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -421B3 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -421B7 CRC F9EDEEDB (4193119963) │ │ │ │ -421BB Compressed Size 00000AC9 (2761) │ │ │ │ -421BF Uncompressed Size 00002133 (8499) │ │ │ │ -421C3 Filename Length 0011 (17) │ │ │ │ -421C5 Extra Length 001C (28) │ │ │ │ -421C7 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x421C7: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -421D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -421DA Length 0009 (9) │ │ │ │ -421DC Flags 03 (3) 'Modification Access' │ │ │ │ -421DD Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -421E1 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -421E5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -421E7 Length 000B (11) │ │ │ │ -421E9 Version 01 (1) │ │ │ │ -421EA UID Size 04 (4) │ │ │ │ -421EB UID 00000000 (0) │ │ │ │ -421EF GID Size 04 (4) │ │ │ │ -421F0 GID 00000000 (0) │ │ │ │ -421F4 PAYLOAD │ │ │ │ - │ │ │ │ -42CBD LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ -42CC1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -42CC2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -42CC3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -42CC5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -42CC7 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -42CCB CRC 061A4CFE (102386942) │ │ │ │ -42CCF Compressed Size 000003FD (1021) │ │ │ │ -42CD3 Uncompressed Size 00000F0C (3852) │ │ │ │ -42CD7 Filename Length 0014 (20) │ │ │ │ -42CD9 Extra Length 001C (28) │ │ │ │ -42CDB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x42CDB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -42CEF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -42CF1 Length 0009 (9) │ │ │ │ -42CF3 Flags 03 (3) 'Modification Access' │ │ │ │ -42CF4 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -42CF8 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -42CFC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -42CFE Length 000B (11) │ │ │ │ -42D00 Version 01 (1) │ │ │ │ -42D01 UID Size 04 (4) │ │ │ │ -42D02 UID 00000000 (0) │ │ │ │ -42D06 GID Size 04 (4) │ │ │ │ -42D07 GID 00000000 (0) │ │ │ │ -42D0B PAYLOAD │ │ │ │ - │ │ │ │ -43108 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -4310C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4310D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4310E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -43110 Compression Method 0008 (8) 'Deflated' │ │ │ │ -43112 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -43116 CRC 3F5B0F93 (1062932371) │ │ │ │ -4311A Compressed Size 00001260 (4704) │ │ │ │ -4311E Uncompressed Size 0000346B (13419) │ │ │ │ -43122 Filename Length 0014 (20) │ │ │ │ -43124 Extra Length 001C (28) │ │ │ │ -43126 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x43126: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4313A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4313C Length 0009 (9) │ │ │ │ -4313E Flags 03 (3) 'Modification Access' │ │ │ │ -4313F Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -43143 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -43147 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -43149 Length 000B (11) │ │ │ │ -4314B Version 01 (1) │ │ │ │ -4314C UID Size 04 (4) │ │ │ │ -4314D UID 00000000 (0) │ │ │ │ -43151 GID Size 04 (4) │ │ │ │ -43152 GID 00000000 (0) │ │ │ │ -43156 PAYLOAD │ │ │ │ - │ │ │ │ -443B6 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ -443BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -443BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -443BC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -443BE Compression Method 0008 (8) 'Deflated' │ │ │ │ -443C0 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -443C4 CRC 41B13910 (1102133520) │ │ │ │ -443C8 Compressed Size 00000ACC (2764) │ │ │ │ -443CC Uncompressed Size 000022FF (8959) │ │ │ │ -443D0 Filename Length 001B (27) │ │ │ │ -443D2 Extra Length 001C (28) │ │ │ │ -443D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x443D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -443EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -443F1 Length 0009 (9) │ │ │ │ -443F3 Flags 03 (3) 'Modification Access' │ │ │ │ -443F4 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -443F8 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -443FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -443FE Length 000B (11) │ │ │ │ -44400 Version 01 (1) │ │ │ │ -44401 UID Size 04 (4) │ │ │ │ -44402 UID 00000000 (0) │ │ │ │ -44406 GID Size 04 (4) │ │ │ │ -44407 GID 00000000 (0) │ │ │ │ -4440B PAYLOAD │ │ │ │ - │ │ │ │ -44ED7 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ -44EDB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -44EDC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -44EDD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -44EDF Compression Method 0008 (8) 'Deflated' │ │ │ │ -44EE1 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -44EE5 CRC DB2E23B4 (3677234100) │ │ │ │ -44EE9 Compressed Size 00000C51 (3153) │ │ │ │ -44EED Uncompressed Size 00002742 (10050) │ │ │ │ -44EF1 Filename Length 0013 (19) │ │ │ │ -44EF3 Extra Length 001C (28) │ │ │ │ -44EF5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x44EF5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -44F08 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -44F0A Length 0009 (9) │ │ │ │ -44F0C Flags 03 (3) 'Modification Access' │ │ │ │ -44F0D Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -44F11 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -44F15 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -44F17 Length 000B (11) │ │ │ │ -44F19 Version 01 (1) │ │ │ │ -44F1A UID Size 04 (4) │ │ │ │ -44F1B UID 00000000 (0) │ │ │ │ -44F1F GID Size 04 (4) │ │ │ │ -44F20 GID 00000000 (0) │ │ │ │ -44F24 PAYLOAD │ │ │ │ - │ │ │ │ -45B75 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -45B79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -45B7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -45B7B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -45B7D Compression Method 0008 (8) 'Deflated' │ │ │ │ -45B7F Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -45B83 CRC 0705BEAC (117817004) │ │ │ │ -45B87 Compressed Size 00000C95 (3221) │ │ │ │ -45B8B Uncompressed Size 00003D11 (15633) │ │ │ │ -45B8F Filename Length 0014 (20) │ │ │ │ -45B91 Extra Length 001C (28) │ │ │ │ -45B93 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45B93: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45BA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -45BA9 Length 0009 (9) │ │ │ │ -45BAB Flags 03 (3) 'Modification Access' │ │ │ │ -45BAC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -45BB0 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -45BB4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45BB6 Length 000B (11) │ │ │ │ -45BB8 Version 01 (1) │ │ │ │ -45BB9 UID Size 04 (4) │ │ │ │ -45BBA UID 00000000 (0) │ │ │ │ -45BBE GID Size 04 (4) │ │ │ │ -45BBF GID 00000000 (0) │ │ │ │ -45BC3 PAYLOAD │ │ │ │ - │ │ │ │ -46858 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -4685C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4685D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4685E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -46860 Compression Method 0008 (8) 'Deflated' │ │ │ │ -46862 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -46866 CRC 356E7AD6 (896432854) │ │ │ │ -4686A Compressed Size 00000F43 (3907) │ │ │ │ -4686E Uncompressed Size 00003744 (14148) │ │ │ │ -46872 Filename Length 000F (15) │ │ │ │ -46874 Extra Length 001C (28) │ │ │ │ -46876 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x46876: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -46885 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -46887 Length 0009 (9) │ │ │ │ -46889 Flags 03 (3) 'Modification Access' │ │ │ │ -4688A Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4688E Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -46892 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -46894 Length 000B (11) │ │ │ │ -46896 Version 01 (1) │ │ │ │ -46897 UID Size 04 (4) │ │ │ │ -46898 UID 00000000 (0) │ │ │ │ -4689C GID Size 04 (4) │ │ │ │ -4689D GID 00000000 (0) │ │ │ │ -468A1 PAYLOAD │ │ │ │ - │ │ │ │ -477E4 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -477E8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -477E9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -477EA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -477EC Compression Method 0008 (8) 'Deflated' │ │ │ │ -477EE Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -477F2 CRC AD08D6E7 (2903037671) │ │ │ │ -477F6 Compressed Size 000006CE (1742) │ │ │ │ -477FA Uncompressed Size 00001AC4 (6852) │ │ │ │ -477FE Filename Length 000F (15) │ │ │ │ -47800 Extra Length 001C (28) │ │ │ │ -47802 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x47802: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -47811 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -47813 Length 0009 (9) │ │ │ │ -47815 Flags 03 (3) 'Modification Access' │ │ │ │ -47816 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4781A Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4781E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -47820 Length 000B (11) │ │ │ │ -47822 Version 01 (1) │ │ │ │ -47823 UID Size 04 (4) │ │ │ │ -47824 UID 00000000 (0) │ │ │ │ -47828 GID Size 04 (4) │ │ │ │ -47829 GID 00000000 (0) │ │ │ │ -4782D PAYLOAD │ │ │ │ - │ │ │ │ -47EFB LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -47EFF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -47F00 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -47F01 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -47F03 Compression Method 0008 (8) 'Deflated' │ │ │ │ -47F05 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -47F09 CRC 27AAA5BB (665494971) │ │ │ │ -47F0D Compressed Size 00001A4F (6735) │ │ │ │ -47F11 Uncompressed Size 0000650E (25870) │ │ │ │ -47F15 Filename Length 0013 (19) │ │ │ │ -47F17 Extra Length 001C (28) │ │ │ │ -47F19 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x47F19: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -47F2C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -47F2E Length 0009 (9) │ │ │ │ -47F30 Flags 03 (3) 'Modification Access' │ │ │ │ -47F31 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -47F35 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -47F39 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -47F3B Length 000B (11) │ │ │ │ -47F3D Version 01 (1) │ │ │ │ -47F3E UID Size 04 (4) │ │ │ │ -47F3F UID 00000000 (0) │ │ │ │ -47F43 GID Size 04 (4) │ │ │ │ -47F44 GID 00000000 (0) │ │ │ │ -47F48 PAYLOAD │ │ │ │ - │ │ │ │ -49997 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -4999B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4999C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4999D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4999F Compression Method 0008 (8) 'Deflated' │ │ │ │ -499A1 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -499A5 CRC 90967BFA (2425781242) │ │ │ │ -499A9 Compressed Size 000009A6 (2470) │ │ │ │ -499AD Uncompressed Size 00001B6A (7018) │ │ │ │ -499B1 Filename Length 0010 (16) │ │ │ │ -499B3 Extra Length 001C (28) │ │ │ │ -499B5 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x499B5: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -499C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -499C7 Length 0009 (9) │ │ │ │ -499C9 Flags 03 (3) 'Modification Access' │ │ │ │ -499CA Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -499CE Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -499D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -499D4 Length 000B (11) │ │ │ │ -499D6 Version 01 (1) │ │ │ │ -499D7 UID Size 04 (4) │ │ │ │ -499D8 UID 00000000 (0) │ │ │ │ -499DC GID Size 04 (4) │ │ │ │ -499DD GID 00000000 (0) │ │ │ │ -499E1 PAYLOAD │ │ │ │ - │ │ │ │ -4A387 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -4A38B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4A38C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4A38D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4A38F Compression Method 0008 (8) 'Deflated' │ │ │ │ -4A391 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -4A395 CRC D37ED789 (3548305289) │ │ │ │ -4A399 Compressed Size 000006B6 (1718) │ │ │ │ -4A39D Uncompressed Size 00001565 (5477) │ │ │ │ -4A3A1 Filename Length 0012 (18) │ │ │ │ -4A3A3 Extra Length 001C (28) │ │ │ │ -4A3A5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4A3A5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4A3B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4A3B9 Length 0009 (9) │ │ │ │ -4A3BB Flags 03 (3) 'Modification Access' │ │ │ │ -4A3BC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4A3C0 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4A3C4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4A3C6 Length 000B (11) │ │ │ │ -4A3C8 Version 01 (1) │ │ │ │ -4A3C9 UID Size 04 (4) │ │ │ │ -4A3CA UID 00000000 (0) │ │ │ │ -4A3CE GID Size 04 (4) │ │ │ │ -4A3CF GID 00000000 (0) │ │ │ │ -4A3D3 PAYLOAD │ │ │ │ - │ │ │ │ -4AA89 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -4AA8D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4AA8E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4AA8F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4AA91 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4AA93 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -4AA97 CRC 92D091A2 (2463142306) │ │ │ │ -4AA9B Compressed Size 00002D59 (11609) │ │ │ │ -4AA9F Uncompressed Size 0000D083 (53379) │ │ │ │ -4AAA3 Filename Length 0010 (16) │ │ │ │ -4AAA5 Extra Length 001C (28) │ │ │ │ -4AAA7 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4AAA7: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4AAB7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4AAB9 Length 0009 (9) │ │ │ │ -4AABB Flags 03 (3) 'Modification Access' │ │ │ │ -4AABC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4AAC0 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4AAC4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4AAC6 Length 000B (11) │ │ │ │ -4AAC8 Version 01 (1) │ │ │ │ -4AAC9 UID Size 04 (4) │ │ │ │ -4AACA UID 00000000 (0) │ │ │ │ -4AACE GID Size 04 (4) │ │ │ │ -4AACF GID 00000000 (0) │ │ │ │ -4AAD3 PAYLOAD │ │ │ │ - │ │ │ │ -4D82C LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -4D830 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4D831 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4D832 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4D834 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4D836 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -4D83A CRC 608FA1E3 (1620025827) │ │ │ │ -4D83E Compressed Size 00001E84 (7812) │ │ │ │ -4D842 Uncompressed Size 00009AAA (39594) │ │ │ │ -4D846 Filename Length 0012 (18) │ │ │ │ -4D848 Extra Length 001C (28) │ │ │ │ -4D84A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4D84A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4D85C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4D85E Length 0009 (9) │ │ │ │ -4D860 Flags 03 (3) 'Modification Access' │ │ │ │ -4D861 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4D865 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4D869 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4D86B Length 000B (11) │ │ │ │ -4D86D Version 01 (1) │ │ │ │ -4D86E UID Size 04 (4) │ │ │ │ -4D86F UID 00000000 (0) │ │ │ │ -4D873 GID Size 04 (4) │ │ │ │ -4D874 GID 00000000 (0) │ │ │ │ -4D878 PAYLOAD │ │ │ │ - │ │ │ │ -4F6FC LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -4F700 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F701 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F702 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F704 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F706 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -4F70A CRC 69109545 (1762694469) │ │ │ │ -4F70E Compressed Size 00001471 (5233) │ │ │ │ -4F712 Uncompressed Size 00007AD0 (31440) │ │ │ │ -4F716 Filename Length 0018 (24) │ │ │ │ -4F718 Extra Length 001C (28) │ │ │ │ -4F71A Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F71A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F732 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F734 Length 0009 (9) │ │ │ │ -4F736 Flags 03 (3) 'Modification Access' │ │ │ │ -4F737 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4F73B Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -4F73F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F741 Length 000B (11) │ │ │ │ -4F743 Version 01 (1) │ │ │ │ -4F744 UID Size 04 (4) │ │ │ │ -4F745 UID 00000000 (0) │ │ │ │ -4F749 GID Size 04 (4) │ │ │ │ -4F74A GID 00000000 (0) │ │ │ │ -4F74E PAYLOAD │ │ │ │ - │ │ │ │ -50BBF LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -50BC3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -50BC4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -50BC5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -50BC7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -50BC9 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -50BCD CRC 274FF23B (659550779) │ │ │ │ -50BD1 Compressed Size 000021DB (8667) │ │ │ │ -50BD5 Uncompressed Size 0000D21D (53789) │ │ │ │ -50BD9 Filename Length 001F (31) │ │ │ │ -50BDB Extra Length 001C (28) │ │ │ │ -50BDD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x50BDD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -50BFC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -50BFE Length 0009 (9) │ │ │ │ -50C00 Flags 03 (3) 'Modification Access' │ │ │ │ -50C01 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -50C05 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -50C09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -50C0B Length 000B (11) │ │ │ │ -50C0D Version 01 (1) │ │ │ │ -50C0E UID Size 04 (4) │ │ │ │ -50C0F UID 00000000 (0) │ │ │ │ -50C13 GID Size 04 (4) │ │ │ │ -50C14 GID 00000000 (0) │ │ │ │ -50C18 PAYLOAD │ │ │ │ - │ │ │ │ -52DF3 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -52DF7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -52DF8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -52DF9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -52DFB Compression Method 0008 (8) 'Deflated' │ │ │ │ -52DFD Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -52E01 CRC 585AC976 (1482344822) │ │ │ │ -52E05 Compressed Size 000003F7 (1015) │ │ │ │ -52E09 Uncompressed Size 000008A3 (2211) │ │ │ │ -52E0D Filename Length 001E (30) │ │ │ │ -52E0F Extra Length 001C (28) │ │ │ │ -52E11 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x52E11: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -52E2F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -52E31 Length 0009 (9) │ │ │ │ -52E33 Flags 03 (3) 'Modification Access' │ │ │ │ -52E34 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -52E38 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -52E3C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -52E3E Length 000B (11) │ │ │ │ -52E40 Version 01 (1) │ │ │ │ -52E41 UID Size 04 (4) │ │ │ │ -52E42 UID 00000000 (0) │ │ │ │ -52E46 GID Size 04 (4) │ │ │ │ -52E47 GID 00000000 (0) │ │ │ │ -52E4B PAYLOAD │ │ │ │ - │ │ │ │ -53242 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -53246 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -53247 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -53248 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5324A Compression Method 0008 (8) 'Deflated' │ │ │ │ -5324C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -53250 CRC 16308EDA (372281050) │ │ │ │ -53254 Compressed Size 00004361 (17249) │ │ │ │ -53258 Uncompressed Size 0000E06F (57455) │ │ │ │ -5325C Filename Length 0013 (19) │ │ │ │ -5325E Extra Length 001C (28) │ │ │ │ -53260 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x53260: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -53273 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -53275 Length 0009 (9) │ │ │ │ -53277 Flags 03 (3) 'Modification Access' │ │ │ │ -53278 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5327C Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -53280 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -53282 Length 000B (11) │ │ │ │ -53284 Version 01 (1) │ │ │ │ -53285 UID Size 04 (4) │ │ │ │ -53286 UID 00000000 (0) │ │ │ │ -5328A GID Size 04 (4) │ │ │ │ -5328B GID 00000000 (0) │ │ │ │ -5328F PAYLOAD │ │ │ │ - │ │ │ │ -575F0 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -575F4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -575F5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -575F6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -575F8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -575FA Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -575FE CRC 741EF03C (1948184636) │ │ │ │ -57602 Compressed Size 000026C3 (9923) │ │ │ │ -57606 Uncompressed Size 00006E45 (28229) │ │ │ │ -5760A Filename Length 0019 (25) │ │ │ │ -5760C Extra Length 001C (28) │ │ │ │ -5760E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5760E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -57627 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -57629 Length 0009 (9) │ │ │ │ -5762B Flags 03 (3) 'Modification Access' │ │ │ │ -5762C Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -57630 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -57634 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -57636 Length 000B (11) │ │ │ │ -57638 Version 01 (1) │ │ │ │ -57639 UID Size 04 (4) │ │ │ │ -5763A UID 00000000 (0) │ │ │ │ -5763E GID Size 04 (4) │ │ │ │ -5763F GID 00000000 (0) │ │ │ │ -57643 PAYLOAD │ │ │ │ - │ │ │ │ -59D06 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -59D0A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -59D0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -59D0C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -59D0E Compression Method 0008 (8) 'Deflated' │ │ │ │ -59D10 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -59D14 CRC F8618863 (4167141475) │ │ │ │ -59D18 Compressed Size 00002738 (10040) │ │ │ │ -59D1C Uncompressed Size 00008B83 (35715) │ │ │ │ -59D20 Filename Length 0019 (25) │ │ │ │ -59D22 Extra Length 001C (28) │ │ │ │ -59D24 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x59D24: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -59D3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -59D3F Length 0009 (9) │ │ │ │ -59D41 Flags 03 (3) 'Modification Access' │ │ │ │ -59D42 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -59D46 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -59D4A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -59D4C Length 000B (11) │ │ │ │ -59D4E Version 01 (1) │ │ │ │ -59D4F UID Size 04 (4) │ │ │ │ -59D50 UID 00000000 (0) │ │ │ │ -59D54 GID Size 04 (4) │ │ │ │ -59D55 GID 00000000 (0) │ │ │ │ -59D59 PAYLOAD │ │ │ │ - │ │ │ │ -5C491 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -5C495 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5C496 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5C497 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5C499 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5C49B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -5C49F CRC 50018C09 (1342278665) │ │ │ │ -5C4A3 Compressed Size 00000ECF (3791) │ │ │ │ -5C4A7 Uncompressed Size 000053BF (21439) │ │ │ │ -5C4AB Filename Length 0021 (33) │ │ │ │ -5C4AD Extra Length 001C (28) │ │ │ │ -5C4AF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5C4AF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5C4D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5C4D2 Length 0009 (9) │ │ │ │ -5C4D4 Flags 03 (3) 'Modification Access' │ │ │ │ -5C4D5 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5C4D9 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5C4DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5C4DF Length 000B (11) │ │ │ │ -5C4E1 Version 01 (1) │ │ │ │ -5C4E2 UID Size 04 (4) │ │ │ │ -5C4E3 UID 00000000 (0) │ │ │ │ -5C4E7 GID Size 04 (4) │ │ │ │ -5C4E8 GID 00000000 (0) │ │ │ │ -5C4EC PAYLOAD │ │ │ │ - │ │ │ │ -5D3BB LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -5D3BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5D3C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5D3C1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5D3C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5D3C5 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -5D3C9 CRC D1EC3113 (3521917203) │ │ │ │ -5D3CD Compressed Size 00000535 (1333) │ │ │ │ -5D3D1 Uncompressed Size 00000C96 (3222) │ │ │ │ -5D3D5 Filename Length 0017 (23) │ │ │ │ -5D3D7 Extra Length 001C (28) │ │ │ │ -5D3D9 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5D3D9: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5D3F0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5D3F2 Length 0009 (9) │ │ │ │ -5D3F4 Flags 03 (3) 'Modification Access' │ │ │ │ -5D3F5 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5D3F9 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5D3FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5D3FF Length 000B (11) │ │ │ │ -5D401 Version 01 (1) │ │ │ │ -5D402 UID Size 04 (4) │ │ │ │ -5D403 UID 00000000 (0) │ │ │ │ -5D407 GID Size 04 (4) │ │ │ │ -5D408 GID 00000000 (0) │ │ │ │ -5D40C PAYLOAD │ │ │ │ - │ │ │ │ -5D941 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -5D945 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5D946 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5D947 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5D949 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5D94B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -5D94F CRC 9ED6F2D9 (2664887001) │ │ │ │ -5D953 Compressed Size 00000467 (1127) │ │ │ │ -5D957 Uncompressed Size 00000931 (2353) │ │ │ │ -5D95B Filename Length 001B (27) │ │ │ │ -5D95D Extra Length 001C (28) │ │ │ │ -5D95F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5D95F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5D97A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5D97C Length 0009 (9) │ │ │ │ -5D97E Flags 03 (3) 'Modification Access' │ │ │ │ -5D97F Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5D983 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5D987 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5D989 Length 000B (11) │ │ │ │ -5D98B Version 01 (1) │ │ │ │ -5D98C UID Size 04 (4) │ │ │ │ -5D98D UID 00000000 (0) │ │ │ │ -5D991 GID Size 04 (4) │ │ │ │ -5D992 GID 00000000 (0) │ │ │ │ -5D996 PAYLOAD │ │ │ │ - │ │ │ │ -5DDFD LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -5DE01 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5DE02 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5DE03 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5DE05 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5DE07 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -5DE0B CRC BA8B93BA (3129709498) │ │ │ │ -5DE0F Compressed Size 000016F5 (5877) │ │ │ │ -5DE13 Uncompressed Size 00007A86 (31366) │ │ │ │ -5DE17 Filename Length 001F (31) │ │ │ │ -5DE19 Extra Length 001C (28) │ │ │ │ -5DE1B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5DE1B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5DE3A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5DE3C Length 0009 (9) │ │ │ │ -5DE3E Flags 03 (3) 'Modification Access' │ │ │ │ -5DE3F Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5DE43 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5DE47 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5DE49 Length 000B (11) │ │ │ │ -5DE4B Version 01 (1) │ │ │ │ -5DE4C UID Size 04 (4) │ │ │ │ -5DE4D UID 00000000 (0) │ │ │ │ -5DE51 GID Size 04 (4) │ │ │ │ -5DE52 GID 00000000 (0) │ │ │ │ -5DE56 PAYLOAD │ │ │ │ - │ │ │ │ -5F54B LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -5F54F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5F550 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5F551 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5F553 Compression Method 0008 (8) 'Deflated' │ │ │ │ -5F555 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -5F559 CRC 7113BB88 (1897118600) │ │ │ │ -5F55D Compressed Size 00004171 (16753) │ │ │ │ -5F561 Uncompressed Size 0001D163 (119139) │ │ │ │ -5F565 Filename Length 0010 (16) │ │ │ │ -5F567 Extra Length 001C (28) │ │ │ │ -5F569 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5F569: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5F579 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5F57B Length 0009 (9) │ │ │ │ -5F57D Flags 03 (3) 'Modification Access' │ │ │ │ -5F57E Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5F582 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -5F586 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5F588 Length 000B (11) │ │ │ │ -5F58A Version 01 (1) │ │ │ │ -5F58B UID Size 04 (4) │ │ │ │ -5F58C UID 00000000 (0) │ │ │ │ -5F590 GID Size 04 (4) │ │ │ │ -5F591 GID 00000000 (0) │ │ │ │ -5F595 PAYLOAD │ │ │ │ - │ │ │ │ -63706 LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -6370A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6370B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6370C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6370E Compression Method 0008 (8) 'Deflated' │ │ │ │ -63710 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -63714 CRC C55EF92D (3311335725) │ │ │ │ -63718 Compressed Size 00000AE8 (2792) │ │ │ │ -6371C Uncompressed Size 000021E8 (8680) │ │ │ │ -63720 Filename Length 0014 (20) │ │ │ │ -63722 Extra Length 001C (28) │ │ │ │ -63724 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x63724: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63738 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6373A Length 0009 (9) │ │ │ │ -6373C Flags 03 (3) 'Modification Access' │ │ │ │ -6373D Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -63741 Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -63745 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63747 Length 000B (11) │ │ │ │ -63749 Version 01 (1) │ │ │ │ -6374A UID Size 04 (4) │ │ │ │ -6374B UID 00000000 (0) │ │ │ │ -6374F GID Size 04 (4) │ │ │ │ -63750 GID 00000000 (0) │ │ │ │ -63754 PAYLOAD │ │ │ │ - │ │ │ │ -6423C LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -64240 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -64241 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -64242 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -64244 Compression Method 0008 (8) 'Deflated' │ │ │ │ -64246 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -6424A CRC B3826829 (3011668009) │ │ │ │ -6424E Compressed Size 0000B524 (46372) │ │ │ │ -64252 Uncompressed Size 00041755 (268117) │ │ │ │ -64256 Filename Length 0017 (23) │ │ │ │ -64258 Extra Length 001C (28) │ │ │ │ -6425A Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6425A: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -64271 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -64273 Length 0009 (9) │ │ │ │ -64275 Flags 03 (3) 'Modification Access' │ │ │ │ -64276 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -6427A Access Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -6427E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -64280 Length 000B (11) │ │ │ │ -64282 Version 01 (1) │ │ │ │ -64283 UID Size 04 (4) │ │ │ │ -64284 UID 00000000 (0) │ │ │ │ -64288 GID Size 04 (4) │ │ │ │ -64289 GID 00000000 (0) │ │ │ │ -6428D PAYLOAD │ │ │ │ - │ │ │ │ -6F7B1 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -6F7B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6F7B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6F7B7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6F7B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6F7BB Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6F7BF CRC A49E0590 (2761819536) │ │ │ │ -6F7C3 Compressed Size 000003FE (1022) │ │ │ │ -6F7C7 Uncompressed Size 0000093D (2365) │ │ │ │ -6F7CB Filename Length 0013 (19) │ │ │ │ -6F7CD Extra Length 001C (28) │ │ │ │ -6F7CF Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6F7CF: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6F7E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6F7E4 Length 0009 (9) │ │ │ │ -6F7E6 Flags 03 (3) 'Modification Access' │ │ │ │ -6F7E7 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6F7EB Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6F7EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6F7F1 Length 000B (11) │ │ │ │ -6F7F3 Version 01 (1) │ │ │ │ -6F7F4 UID Size 04 (4) │ │ │ │ -6F7F5 UID 00000000 (0) │ │ │ │ -6F7F9 GID Size 04 (4) │ │ │ │ -6F7FA GID 00000000 (0) │ │ │ │ -6F7FE PAYLOAD │ │ │ │ - │ │ │ │ -6FBFC LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -6FC00 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6FC01 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6FC02 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6FC04 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6FC06 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6FC0A CRC 1FD7FA48 (534248008) │ │ │ │ -6FC0E Compressed Size 000014D8 (5336) │ │ │ │ -6FC12 Uncompressed Size 00006892 (26770) │ │ │ │ -6FC16 Filename Length 0012 (18) │ │ │ │ -6FC18 Extra Length 001C (28) │ │ │ │ -6FC1A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6FC1A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6FC2C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6FC2E Length 0009 (9) │ │ │ │ -6FC30 Flags 03 (3) 'Modification Access' │ │ │ │ -6FC31 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6FC35 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -6FC39 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6FC3B Length 000B (11) │ │ │ │ -6FC3D Version 01 (1) │ │ │ │ -6FC3E UID Size 04 (4) │ │ │ │ -6FC3F UID 00000000 (0) │ │ │ │ -6FC43 GID Size 04 (4) │ │ │ │ -6FC44 GID 00000000 (0) │ │ │ │ -6FC48 PAYLOAD │ │ │ │ - │ │ │ │ -71120 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -71124 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -71125 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -71126 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -71128 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7112A Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7112E CRC 7167272B (1902585643) │ │ │ │ -71132 Compressed Size 00001205 (4613) │ │ │ │ -71136 Uncompressed Size 0000414F (16719) │ │ │ │ -7113A Filename Length 0012 (18) │ │ │ │ -7113C Extra Length 001C (28) │ │ │ │ -7113E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7113E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -71150 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -71152 Length 0009 (9) │ │ │ │ -71154 Flags 03 (3) 'Modification Access' │ │ │ │ -71155 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -71159 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7115D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7115F Length 000B (11) │ │ │ │ -71161 Version 01 (1) │ │ │ │ -71162 UID Size 04 (4) │ │ │ │ -71163 UID 00000000 (0) │ │ │ │ -71167 GID Size 04 (4) │ │ │ │ -71168 GID 00000000 (0) │ │ │ │ -7116C PAYLOAD │ │ │ │ - │ │ │ │ -72371 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -72375 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -72376 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -72377 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -72379 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7237B Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7237F CRC 5C8A4BDC (1552567260) │ │ │ │ -72383 Compressed Size 00000704 (1796) │ │ │ │ -72387 Uncompressed Size 000011A7 (4519) │ │ │ │ -7238B Filename Length 0019 (25) │ │ │ │ -7238D Extra Length 001C (28) │ │ │ │ -7238F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7238F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -723A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -723AA Length 0009 (9) │ │ │ │ -723AC Flags 03 (3) 'Modification Access' │ │ │ │ -723AD Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -723B1 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -723B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -723B7 Length 000B (11) │ │ │ │ -723B9 Version 01 (1) │ │ │ │ -723BA UID Size 04 (4) │ │ │ │ -723BB UID 00000000 (0) │ │ │ │ -723BF GID Size 04 (4) │ │ │ │ -723C0 GID 00000000 (0) │ │ │ │ -723C4 PAYLOAD │ │ │ │ - │ │ │ │ -72AC8 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -72ACC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -72ACD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -72ACE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -72AD0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -72AD2 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -72AD6 CRC 337967C1 (863594433) │ │ │ │ -72ADA Compressed Size 000018B8 (6328) │ │ │ │ -72ADE Uncompressed Size 0000A678 (42616) │ │ │ │ -72AE2 Filename Length 0019 (25) │ │ │ │ -72AE4 Extra Length 001C (28) │ │ │ │ -72AE6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x72AE6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -72AFF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -72B01 Length 0009 (9) │ │ │ │ -72B03 Flags 03 (3) 'Modification Access' │ │ │ │ -72B04 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -72B08 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -72B0C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -72B0E Length 000B (11) │ │ │ │ -72B10 Version 01 (1) │ │ │ │ -72B11 UID Size 04 (4) │ │ │ │ -72B12 UID 00000000 (0) │ │ │ │ -72B16 GID Size 04 (4) │ │ │ │ -72B17 GID 00000000 (0) │ │ │ │ -72B1B PAYLOAD │ │ │ │ - │ │ │ │ -743D3 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -743D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -743D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -743D9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -743DB Compression Method 0008 (8) 'Deflated' │ │ │ │ -743DD Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -743E1 CRC 4D1409E6 (1293158886) │ │ │ │ -743E5 Compressed Size 0000177C (6012) │ │ │ │ -743E9 Uncompressed Size 0000472C (18220) │ │ │ │ -743ED Filename Length 0014 (20) │ │ │ │ -743EF Extra Length 001C (28) │ │ │ │ -743F1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x743F1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -74405 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -74407 Length 0009 (9) │ │ │ │ -74409 Flags 03 (3) 'Modification Access' │ │ │ │ -7440A Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7440E Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -74412 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -74414 Length 000B (11) │ │ │ │ -74416 Version 01 (1) │ │ │ │ -74417 UID Size 04 (4) │ │ │ │ -74418 UID 00000000 (0) │ │ │ │ -7441C GID Size 04 (4) │ │ │ │ -7441D GID 00000000 (0) │ │ │ │ -74421 PAYLOAD │ │ │ │ - │ │ │ │ -75B9D LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -75BA1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -75BA2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -75BA3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -75BA5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -75BA7 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -75BAB CRC 2F723DFF (796016127) │ │ │ │ -75BAF Compressed Size 00000409 (1033) │ │ │ │ -75BB3 Uncompressed Size 00000825 (2085) │ │ │ │ -75BB7 Filename Length 001C (28) │ │ │ │ -75BB9 Extra Length 001C (28) │ │ │ │ -75BBB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x75BBB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -75BD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -75BD9 Length 0009 (9) │ │ │ │ -75BDB Flags 03 (3) 'Modification Access' │ │ │ │ -75BDC Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -75BE0 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -75BE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -75BE6 Length 000B (11) │ │ │ │ -75BE8 Version 01 (1) │ │ │ │ -75BE9 UID Size 04 (4) │ │ │ │ -75BEA UID 00000000 (0) │ │ │ │ -75BEE GID Size 04 (4) │ │ │ │ -75BEF GID 00000000 (0) │ │ │ │ -75BF3 PAYLOAD │ │ │ │ - │ │ │ │ -75FFC LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -76000 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -76001 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -76002 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -76004 Compression Method 0008 (8) 'Deflated' │ │ │ │ -76006 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7600A CRC CCD906B0 (3436775088) │ │ │ │ -7600E Compressed Size 000024C6 (9414) │ │ │ │ -76012 Uncompressed Size 0000B65D (46685) │ │ │ │ -76016 Filename Length 001F (31) │ │ │ │ -76018 Extra Length 001C (28) │ │ │ │ -7601A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7601A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -76039 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7603B Length 0009 (9) │ │ │ │ -7603D Flags 03 (3) 'Modification Access' │ │ │ │ -7603E Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -76042 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -76046 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -76048 Length 000B (11) │ │ │ │ -7604A Version 01 (1) │ │ │ │ -7604B UID Size 04 (4) │ │ │ │ -7604C UID 00000000 (0) │ │ │ │ -76050 GID Size 04 (4) │ │ │ │ -76051 GID 00000000 (0) │ │ │ │ -76055 PAYLOAD │ │ │ │ - │ │ │ │ -7851B LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -7851F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -78520 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -78521 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -78523 Compression Method 0008 (8) 'Deflated' │ │ │ │ -78525 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -78529 CRC E78CD1A8 (3884765608) │ │ │ │ -7852D Compressed Size 00000E7C (3708) │ │ │ │ -78531 Uncompressed Size 000052DA (21210) │ │ │ │ -78535 Filename Length 001F (31) │ │ │ │ -78537 Extra Length 001C (28) │ │ │ │ -78539 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x78539: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -78558 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7855A Length 0009 (9) │ │ │ │ -7855C Flags 03 (3) 'Modification Access' │ │ │ │ -7855D Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -78561 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -78565 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -78567 Length 000B (11) │ │ │ │ -78569 Version 01 (1) │ │ │ │ -7856A UID Size 04 (4) │ │ │ │ -7856B UID 00000000 (0) │ │ │ │ -7856F GID Size 04 (4) │ │ │ │ -78570 GID 00000000 (0) │ │ │ │ -78574 PAYLOAD │ │ │ │ - │ │ │ │ -793F0 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -793F4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -793F5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -793F6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -793F8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -793FA Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -793FE CRC E196989B (3784743067) │ │ │ │ -79402 Compressed Size 00000A44 (2628) │ │ │ │ -79406 Uncompressed Size 0000247A (9338) │ │ │ │ -7940A Filename Length 0013 (19) │ │ │ │ -7940C Extra Length 001C (28) │ │ │ │ -7940E Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7940E: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -79421 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -79423 Length 0009 (9) │ │ │ │ -79425 Flags 03 (3) 'Modification Access' │ │ │ │ -79426 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7942A Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7942E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -79430 Length 000B (11) │ │ │ │ -79432 Version 01 (1) │ │ │ │ -79433 UID Size 04 (4) │ │ │ │ -79434 UID 00000000 (0) │ │ │ │ -79438 GID Size 04 (4) │ │ │ │ -79439 GID 00000000 (0) │ │ │ │ -7943D PAYLOAD │ │ │ │ - │ │ │ │ -79E81 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -79E85 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -79E86 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -79E87 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -79E89 Compression Method 0008 (8) 'Deflated' │ │ │ │ -79E8B Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -79E8F CRC 34AC5B41 (883710785) │ │ │ │ -79E93 Compressed Size 00002591 (9617) │ │ │ │ -79E97 Uncompressed Size 0000BAA4 (47780) │ │ │ │ -79E9B Filename Length 0019 (25) │ │ │ │ -79E9D Extra Length 001C (28) │ │ │ │ -79E9F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x79E9F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -79EB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -79EBA Length 0009 (9) │ │ │ │ -79EBC Flags 03 (3) 'Modification Access' │ │ │ │ -79EBD Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -79EC1 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -79EC5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -79EC7 Length 000B (11) │ │ │ │ -79EC9 Version 01 (1) │ │ │ │ -79ECA UID Size 04 (4) │ │ │ │ -79ECB UID 00000000 (0) │ │ │ │ -79ECF GID Size 04 (4) │ │ │ │ -79ED0 GID 00000000 (0) │ │ │ │ -79ED4 PAYLOAD │ │ │ │ - │ │ │ │ -7C465 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -7C469 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7C46A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7C46B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7C46D Compression Method 0008 (8) 'Deflated' │ │ │ │ -7C46F Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7C473 CRC 97E60E7F (2548436607) │ │ │ │ -7C477 Compressed Size 00000EFD (3837) │ │ │ │ -7C47B Uncompressed Size 00003A2F (14895) │ │ │ │ -7C47F Filename Length 0024 (36) │ │ │ │ -7C481 Extra Length 001C (28) │ │ │ │ -7C483 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7C483: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7C4A7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7C4A9 Length 0009 (9) │ │ │ │ -7C4AB Flags 03 (3) 'Modification Access' │ │ │ │ -7C4AC Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7C4B0 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7C4B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7C4B6 Length 000B (11) │ │ │ │ -7C4B8 Version 01 (1) │ │ │ │ -7C4B9 UID Size 04 (4) │ │ │ │ -7C4BA UID 00000000 (0) │ │ │ │ -7C4BE GID Size 04 (4) │ │ │ │ -7C4BF GID 00000000 (0) │ │ │ │ -7C4C3 PAYLOAD │ │ │ │ - │ │ │ │ -7D3C0 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -7D3C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7D3C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7D3C6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7D3C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7D3CA Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7D3CE CRC 247F076B (612304747) │ │ │ │ -7D3D2 Compressed Size 00001AEA (6890) │ │ │ │ -7D3D6 Uncompressed Size 00005F7F (24447) │ │ │ │ -7D3DA Filename Length 0017 (23) │ │ │ │ -7D3DC Extra Length 001C (28) │ │ │ │ -7D3DE Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7D3DE: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7D3F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7D3F7 Length 0009 (9) │ │ │ │ -7D3F9 Flags 03 (3) 'Modification Access' │ │ │ │ -7D3FA Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7D3FE Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7D402 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7D404 Length 000B (11) │ │ │ │ -7D406 Version 01 (1) │ │ │ │ -7D407 UID Size 04 (4) │ │ │ │ -7D408 UID 00000000 (0) │ │ │ │ -7D40C GID Size 04 (4) │ │ │ │ -7D40D GID 00000000 (0) │ │ │ │ -7D411 PAYLOAD │ │ │ │ - │ │ │ │ -7EEFB LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -7EEFF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7EF00 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7EF01 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7EF03 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7EF05 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7EF09 CRC 11E32AF1 (300100337) │ │ │ │ -7EF0D Compressed Size 00000ED3 (3795) │ │ │ │ -7EF11 Uncompressed Size 000038E2 (14562) │ │ │ │ -7EF15 Filename Length 0023 (35) │ │ │ │ -7EF17 Extra Length 001C (28) │ │ │ │ -7EF19 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7EF19: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7EF3C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7EF3E Length 0009 (9) │ │ │ │ -7EF40 Flags 03 (3) 'Modification Access' │ │ │ │ -7EF41 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7EF45 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7EF49 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7EF4B Length 000B (11) │ │ │ │ -7EF4D Version 01 (1) │ │ │ │ -7EF4E UID Size 04 (4) │ │ │ │ -7EF4F UID 00000000 (0) │ │ │ │ -7EF53 GID Size 04 (4) │ │ │ │ -7EF54 GID 00000000 (0) │ │ │ │ -7EF58 PAYLOAD │ │ │ │ - │ │ │ │ -7FE2B LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -7FE2F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7FE30 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7FE31 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7FE33 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7FE35 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FE39 CRC 2DB7929F (767005343) │ │ │ │ -7FE3D Compressed Size 00000113 (275) │ │ │ │ -7FE41 Uncompressed Size 000001F3 (499) │ │ │ │ -7FE45 Filename Length 001B (27) │ │ │ │ -7FE47 Extra Length 001C (28) │ │ │ │ -7FE49 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7FE49: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7FE64 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7FE66 Length 0009 (9) │ │ │ │ -7FE68 Flags 03 (3) 'Modification Access' │ │ │ │ -7FE69 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FE6D Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FE71 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7FE73 Length 000B (11) │ │ │ │ -7FE75 Version 01 (1) │ │ │ │ -7FE76 UID Size 04 (4) │ │ │ │ -7FE77 UID 00000000 (0) │ │ │ │ -7FE7B GID Size 04 (4) │ │ │ │ -7FE7C GID 00000000 (0) │ │ │ │ -7FE80 PAYLOAD │ │ │ │ - │ │ │ │ -7FF93 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -7FF97 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7FF98 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7FF99 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7FF9B Compression Method 0008 (8) 'Deflated' │ │ │ │ -7FF9D Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FFA1 CRC 3E2ED2F5 (1043256053) │ │ │ │ -7FFA5 Compressed Size 0000188D (6285) │ │ │ │ -7FFA9 Uncompressed Size 00008FA8 (36776) │ │ │ │ -7FFAD Filename Length 001D (29) │ │ │ │ -7FFAF Extra Length 001C (28) │ │ │ │ -7FFB1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7FFB1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7FFCE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7FFD0 Length 0009 (9) │ │ │ │ -7FFD2 Flags 03 (3) 'Modification Access' │ │ │ │ -7FFD3 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FFD7 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -7FFDB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7FFDD Length 000B (11) │ │ │ │ -7FFDF Version 01 (1) │ │ │ │ -7FFE0 UID Size 04 (4) │ │ │ │ -7FFE1 UID 00000000 (0) │ │ │ │ -7FFE5 GID Size 04 (4) │ │ │ │ -7FFE6 GID 00000000 (0) │ │ │ │ -7FFEA PAYLOAD │ │ │ │ - │ │ │ │ -81877 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -8187B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8187C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8187D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8187F Compression Method 0008 (8) 'Deflated' │ │ │ │ -81881 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -81885 CRC 13F8A234 (335061556) │ │ │ │ -81889 Compressed Size 0000164C (5708) │ │ │ │ -8188D Uncompressed Size 00003A9B (15003) │ │ │ │ -81891 Filename Length 0015 (21) │ │ │ │ -81893 Extra Length 001C (28) │ │ │ │ -81895 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81895: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -818AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -818AC Length 0009 (9) │ │ │ │ -818AE Flags 03 (3) 'Modification Access' │ │ │ │ -818AF Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -818B3 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -818B7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -818B9 Length 000B (11) │ │ │ │ -818BB Version 01 (1) │ │ │ │ -818BC UID Size 04 (4) │ │ │ │ -818BD UID 00000000 (0) │ │ │ │ -818C1 GID Size 04 (4) │ │ │ │ -818C2 GID 00000000 (0) │ │ │ │ -818C6 PAYLOAD │ │ │ │ - │ │ │ │ -82F12 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -82F16 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -82F17 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -82F18 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -82F1A Compression Method 0008 (8) 'Deflated' │ │ │ │ -82F1C Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -82F20 CRC 945176DD (2488366813) │ │ │ │ -82F24 Compressed Size 000040CE (16590) │ │ │ │ -82F28 Uncompressed Size 000133AC (78764) │ │ │ │ -82F2C Filename Length 0016 (22) │ │ │ │ -82F2E Extra Length 001C (28) │ │ │ │ -82F30 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x82F30: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -82F46 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -82F48 Length 0009 (9) │ │ │ │ -82F4A Flags 03 (3) 'Modification Access' │ │ │ │ -82F4B Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -82F4F Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -82F53 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -82F55 Length 000B (11) │ │ │ │ -82F57 Version 01 (1) │ │ │ │ -82F58 UID Size 04 (4) │ │ │ │ -82F59 UID 00000000 (0) │ │ │ │ -82F5D GID Size 04 (4) │ │ │ │ -82F5E GID 00000000 (0) │ │ │ │ -82F62 PAYLOAD │ │ │ │ +00DCD LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ +00DD1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +00DD2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +00DD3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +00DD5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +00DD7 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +00DDB CRC 546A35FD (1416246781) │ │ │ │ +00DDF Compressed Size 000015B1 (5553) │ │ │ │ +00DE3 Uncompressed Size 00004605 (17925) │ │ │ │ +00DE7 Filename Length 0014 (20) │ │ │ │ +00DE9 Extra Length 001C (28) │ │ │ │ +00DEB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xDEB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +00DFF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +00E01 Length 0009 (9) │ │ │ │ +00E03 Flags 03 (3) 'Modification Access' │ │ │ │ +00E04 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +00E08 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +00E0C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +00E0E Length 000B (11) │ │ │ │ +00E10 Version 01 (1) │ │ │ │ +00E11 UID Size 04 (4) │ │ │ │ +00E12 UID 00000000 (0) │ │ │ │ +00E16 GID Size 04 (4) │ │ │ │ +00E17 GID 00000000 (0) │ │ │ │ +00E1B 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 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +023DA CRC 03A4A46E (61121646) │ │ │ │ +023DE Compressed Size 000006D4 (1748) │ │ │ │ +023E2 Uncompressed Size 00001241 (4673) │ │ │ │ +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 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +02406 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13: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 │ │ │ │ + │ │ │ │ +02AED LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +02AF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +02AF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +02AF3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +02AF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +02AF7 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +02AFB CRC 69D3269E (1775445662) │ │ │ │ +02AFF Compressed Size 00002E70 (11888) │ │ │ │ +02B03 Uncompressed Size 0000D4C1 (54465) │ │ │ │ +02B07 Filename Length 0014 (20) │ │ │ │ +02B09 Extra Length 001C (28) │ │ │ │ +02B0B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B0B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +02B1F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +02B21 Length 0009 (9) │ │ │ │ +02B23 Flags 03 (3) 'Modification Access' │ │ │ │ +02B24 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +02B28 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +02B2C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +02B2E Length 000B (11) │ │ │ │ +02B30 Version 01 (1) │ │ │ │ +02B31 UID Size 04 (4) │ │ │ │ +02B32 UID 00000000 (0) │ │ │ │ +02B36 GID Size 04 (4) │ │ │ │ +02B37 GID 00000000 (0) │ │ │ │ +02B3B PAYLOAD │ │ │ │ + │ │ │ │ +059AB LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ +059AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +059B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +059B1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +059B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +059B5 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +059B9 CRC 272DD323 (657314595) │ │ │ │ +059BD Compressed Size 000003EF (1007) │ │ │ │ +059C1 Uncompressed Size 00000876 (2166) │ │ │ │ +059C5 Filename Length 0014 (20) │ │ │ │ +059C7 Extra Length 001C (28) │ │ │ │ +059C9 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x59C9: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +059DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +059DF Length 0009 (9) │ │ │ │ +059E1 Flags 03 (3) 'Modification Access' │ │ │ │ +059E2 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +059E6 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +059EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +059EC Length 000B (11) │ │ │ │ +059EE Version 01 (1) │ │ │ │ +059EF UID Size 04 (4) │ │ │ │ +059F0 UID 00000000 (0) │ │ │ │ +059F4 GID Size 04 (4) │ │ │ │ +059F5 GID 00000000 (0) │ │ │ │ +059F9 PAYLOAD │ │ │ │ + │ │ │ │ +05DE8 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ +05DEC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +05DED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +05DEE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +05DF0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +05DF2 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +05DF6 CRC 2F07766F (789018223) │ │ │ │ +05DFA Compressed Size 000001AD (429) │ │ │ │ +05DFE Uncompressed Size 000002FC (764) │ │ │ │ +05E02 Filename Length 0011 (17) │ │ │ │ +05E04 Extra Length 001C (28) │ │ │ │ +05E06 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5E06: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +05E17 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +05E19 Length 0009 (9) │ │ │ │ +05E1B Flags 03 (3) 'Modification Access' │ │ │ │ +05E1C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +05E20 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +05E24 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +05E26 Length 000B (11) │ │ │ │ +05E28 Version 01 (1) │ │ │ │ +05E29 UID Size 04 (4) │ │ │ │ +05E2A UID 00000000 (0) │ │ │ │ +05E2E GID Size 04 (4) │ │ │ │ +05E2F GID 00000000 (0) │ │ │ │ +05E33 PAYLOAD │ │ │ │ + │ │ │ │ +05FE0 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ +05FE4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +05FE5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +05FE6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +05FE8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +05FEA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +05FEE CRC 2E830899 (780339353) │ │ │ │ +05FF2 Compressed Size 000020BD (8381) │ │ │ │ +05FF6 Uncompressed Size 0000B4B1 (46257) │ │ │ │ +05FFA Filename Length 001B (27) │ │ │ │ +05FFC Extra Length 001C (28) │ │ │ │ +05FFE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5FFE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +06019 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0601B Length 0009 (9) │ │ │ │ +0601D Flags 03 (3) 'Modification Access' │ │ │ │ +0601E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +06022 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +06026 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +06028 Length 000B (11) │ │ │ │ +0602A Version 01 (1) │ │ │ │ +0602B UID Size 04 (4) │ │ │ │ +0602C UID 00000000 (0) │ │ │ │ +06030 GID Size 04 (4) │ │ │ │ +06031 GID 00000000 (0) │ │ │ │ +06035 PAYLOAD │ │ │ │ + │ │ │ │ +080F2 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ +080F6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +080F7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +080F8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +080FA Compression Method 0008 (8) 'Deflated' │ │ │ │ +080FC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +08100 CRC 4E72FA06 (1316157958) │ │ │ │ +08104 Compressed Size 00000E68 (3688) │ │ │ │ +08108 Uncompressed Size 00003097 (12439) │ │ │ │ +0810C Filename Length 001D (29) │ │ │ │ +0810E Extra Length 001C (28) │ │ │ │ +08110 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8110: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0812D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0812F Length 0009 (9) │ │ │ │ +08131 Flags 03 (3) 'Modification Access' │ │ │ │ +08132 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +08136 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +0813A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0813C Length 000B (11) │ │ │ │ +0813E Version 01 (1) │ │ │ │ +0813F UID Size 04 (4) │ │ │ │ +08140 UID 00000000 (0) │ │ │ │ +08144 GID Size 04 (4) │ │ │ │ +08145 GID 00000000 (0) │ │ │ │ +08149 PAYLOAD │ │ │ │ + │ │ │ │ +08FB1 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08FB5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08FB6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08FB7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08FB9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +08FBB Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +08FBF CRC F138337A (4046992250) │ │ │ │ +08FC3 Compressed Size 0000098E (2446) │ │ │ │ +08FC7 Uncompressed Size 00001D39 (7481) │ │ │ │ +08FCB Filename Length 0019 (25) │ │ │ │ +08FCD Extra Length 001C (28) │ │ │ │ +08FCF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8FCF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08FE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08FEA Length 0009 (9) │ │ │ │ +08FEC Flags 03 (3) 'Modification Access' │ │ │ │ +08FED Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +08FF1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +08FF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08FF7 Length 000B (11) │ │ │ │ +08FF9 Version 01 (1) │ │ │ │ +08FFA UID Size 04 (4) │ │ │ │ +08FFB UID 00000000 (0) │ │ │ │ +08FFF GID Size 04 (4) │ │ │ │ +09000 GID 00000000 (0) │ │ │ │ +09004 PAYLOAD │ │ │ │ + │ │ │ │ +09992 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +09996 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +09997 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +09998 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0999A Compression Method 0008 (8) 'Deflated' │ │ │ │ +0999C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +099A0 CRC 42AE2C47 (1118710855) │ │ │ │ +099A4 Compressed Size 00003887 (14471) │ │ │ │ +099A8 Uncompressed Size 0000F81F (63519) │ │ │ │ +099AC Filename Length 0015 (21) │ │ │ │ +099AE Extra Length 001C (28) │ │ │ │ +099B0 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x99B0: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +099C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +099C7 Length 0009 (9) │ │ │ │ +099C9 Flags 03 (3) 'Modification Access' │ │ │ │ +099CA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +099CE Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +099D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +099D4 Length 000B (11) │ │ │ │ +099D6 Version 01 (1) │ │ │ │ +099D7 UID Size 04 (4) │ │ │ │ +099D8 UID 00000000 (0) │ │ │ │ +099DC GID Size 04 (4) │ │ │ │ +099DD GID 00000000 (0) │ │ │ │ +099E1 PAYLOAD │ │ │ │ + │ │ │ │ +0D268 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +0D26C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0D26D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0D26E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0D270 Compression Method 0008 (8) 'Deflated' │ │ │ │ +0D272 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +0D276 CRC BCFA2578 (3170510200) │ │ │ │ +0D27A Compressed Size 0000AB01 (43777) │ │ │ │ +0D27E Uncompressed Size 0003E0D8 (254168) │ │ │ │ +0D282 Filename Length 0012 (18) │ │ │ │ +0D284 Extra Length 001C (28) │ │ │ │ +0D286 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xD286: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0D298 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0D29A Length 0009 (9) │ │ │ │ +0D29C Flags 03 (3) 'Modification Access' │ │ │ │ +0D29D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +0D2A1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +0D2A5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0D2A7 Length 000B (11) │ │ │ │ +0D2A9 Version 01 (1) │ │ │ │ +0D2AA UID Size 04 (4) │ │ │ │ +0D2AB UID 00000000 (0) │ │ │ │ +0D2AF GID Size 04 (4) │ │ │ │ +0D2B0 GID 00000000 (0) │ │ │ │ +0D2B4 PAYLOAD │ │ │ │ + │ │ │ │ +17DB5 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ +17DB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +17DBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +17DBB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +17DBD Compression Method 0008 (8) 'Deflated' │ │ │ │ +17DBF Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +17DC3 CRC 52E3F286 (1390670470) │ │ │ │ +17DC7 Compressed Size 00003AF8 (15096) │ │ │ │ +17DCB Uncompressed Size 0001B421 (111649) │ │ │ │ +17DCF Filename Length 0015 (21) │ │ │ │ +17DD1 Extra Length 001C (28) │ │ │ │ +17DD3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x17DD3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +17DE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +17DEA Length 0009 (9) │ │ │ │ +17DEC Flags 03 (3) 'Modification Access' │ │ │ │ +17DED Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +17DF1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +17DF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +17DF7 Length 000B (11) │ │ │ │ +17DF9 Version 01 (1) │ │ │ │ +17DFA UID Size 04 (4) │ │ │ │ +17DFB UID 00000000 (0) │ │ │ │ +17DFF GID Size 04 (4) │ │ │ │ +17E00 GID 00000000 (0) │ │ │ │ +17E04 PAYLOAD │ │ │ │ + │ │ │ │ +1B8FC LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +1B900 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +1B901 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +1B902 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +1B904 Compression Method 0008 (8) 'Deflated' │ │ │ │ +1B906 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +1B90A CRC DBC5E740 (3687180096) │ │ │ │ +1B90E Compressed Size 000091CA (37322) │ │ │ │ +1B912 Uncompressed Size 0003DBD1 (252881) │ │ │ │ +1B916 Filename Length 0014 (20) │ │ │ │ +1B918 Extra Length 001C (28) │ │ │ │ +1B91A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x1B91A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +1B92E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +1B930 Length 0009 (9) │ │ │ │ +1B932 Flags 03 (3) 'Modification Access' │ │ │ │ +1B933 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +1B937 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +1B93B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +1B93D Length 000B (11) │ │ │ │ +1B93F Version 01 (1) │ │ │ │ +1B940 UID Size 04 (4) │ │ │ │ +1B941 UID 00000000 (0) │ │ │ │ +1B945 GID Size 04 (4) │ │ │ │ +1B946 GID 00000000 (0) │ │ │ │ +1B94A PAYLOAD │ │ │ │ + │ │ │ │ +24B14 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +24B18 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +24B19 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +24B1A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +24B1C Compression Method 0008 (8) 'Deflated' │ │ │ │ +24B1E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +24B22 CRC 953AA54E (2503648590) │ │ │ │ +24B26 Compressed Size 00009BA8 (39848) │ │ │ │ +24B2A Uncompressed Size 00027CF5 (163061) │ │ │ │ +24B2E Filename Length 0019 (25) │ │ │ │ +24B30 Extra Length 001C (28) │ │ │ │ +24B32 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x24B32: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +24B4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +24B4D Length 0009 (9) │ │ │ │ +24B4F Flags 03 (3) 'Modification Access' │ │ │ │ +24B50 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +24B54 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +24B58 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +24B5A Length 000B (11) │ │ │ │ +24B5C Version 01 (1) │ │ │ │ +24B5D UID Size 04 (4) │ │ │ │ +24B5E UID 00000000 (0) │ │ │ │ +24B62 GID Size 04 (4) │ │ │ │ +24B63 GID 00000000 (0) │ │ │ │ +24B67 PAYLOAD │ │ │ │ + │ │ │ │ +2E70F LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +2E713 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2E714 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2E715 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2E717 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2E719 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +2E71D CRC 36D4AE72 (919907954) │ │ │ │ +2E721 Compressed Size 00001219 (4633) │ │ │ │ +2E725 Uncompressed Size 00003C91 (15505) │ │ │ │ +2E729 Filename Length 0010 (16) │ │ │ │ +2E72B Extra Length 001C (28) │ │ │ │ +2E72D Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2E72D: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2E73D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2E73F Length 0009 (9) │ │ │ │ +2E741 Flags 03 (3) 'Modification Access' │ │ │ │ +2E742 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +2E746 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +2E74A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2E74C Length 000B (11) │ │ │ │ +2E74E Version 01 (1) │ │ │ │ +2E74F UID Size 04 (4) │ │ │ │ +2E750 UID 00000000 (0) │ │ │ │ +2E754 GID Size 04 (4) │ │ │ │ +2E755 GID 00000000 (0) │ │ │ │ +2E759 PAYLOAD │ │ │ │ + │ │ │ │ +2F972 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +2F976 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2F977 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2F978 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2F97A Compression Method 0008 (8) 'Deflated' │ │ │ │ +2F97C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +2F980 CRC 6E6B7F3A (1852538682) │ │ │ │ +2F984 Compressed Size 00002A60 (10848) │ │ │ │ +2F988 Uncompressed Size 000113A7 (70567) │ │ │ │ +2F98C Filename Length 0016 (22) │ │ │ │ +2F98E Extra Length 001C (28) │ │ │ │ +2F990 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2F990: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2F9A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2F9A8 Length 0009 (9) │ │ │ │ +2F9AA Flags 03 (3) 'Modification Access' │ │ │ │ +2F9AB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +2F9AF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +2F9B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2F9B5 Length 000B (11) │ │ │ │ +2F9B7 Version 01 (1) │ │ │ │ +2F9B8 UID Size 04 (4) │ │ │ │ +2F9B9 UID 00000000 (0) │ │ │ │ +2F9BD GID Size 04 (4) │ │ │ │ +2F9BE GID 00000000 (0) │ │ │ │ +2F9C2 PAYLOAD │ │ │ │ + │ │ │ │ +32422 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ +32426 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32427 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32428 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3242A Compression Method 0008 (8) 'Deflated' │ │ │ │ +3242C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +32430 CRC EC084A66 (3959966310) │ │ │ │ +32434 Compressed Size 000014D8 (5336) │ │ │ │ +32438 Uncompressed Size 0000518D (20877) │ │ │ │ +3243C Filename Length 001D (29) │ │ │ │ +3243E Extra Length 001C (28) │ │ │ │ +32440 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32440: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3245D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3245F Length 0009 (9) │ │ │ │ +32461 Flags 03 (3) 'Modification Access' │ │ │ │ +32462 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +32466 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3246A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3246C Length 000B (11) │ │ │ │ +3246E Version 01 (1) │ │ │ │ +3246F UID Size 04 (4) │ │ │ │ +32470 UID 00000000 (0) │ │ │ │ +32474 GID Size 04 (4) │ │ │ │ +32475 GID 00000000 (0) │ │ │ │ +32479 PAYLOAD │ │ │ │ + │ │ │ │ +33951 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +33955 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +33956 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +33957 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +33959 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3395B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3395F CRC C2410B8F (3259042703) │ │ │ │ +33963 Compressed Size 00003805 (14341) │ │ │ │ +33967 Uncompressed Size 0000EA4B (59979) │ │ │ │ +3396B Filename Length 001C (28) │ │ │ │ +3396D Extra Length 001C (28) │ │ │ │ +3396F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3396F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3398B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3398D Length 0009 (9) │ │ │ │ +3398F Flags 03 (3) 'Modification Access' │ │ │ │ +33990 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +33994 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +33998 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3399A Length 000B (11) │ │ │ │ +3399C Version 01 (1) │ │ │ │ +3399D UID Size 04 (4) │ │ │ │ +3399E UID 00000000 (0) │ │ │ │ +339A2 GID Size 04 (4) │ │ │ │ +339A3 GID 00000000 (0) │ │ │ │ +339A7 PAYLOAD │ │ │ │ + │ │ │ │ +371AC LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +371B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +371B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +371B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +371B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +371B6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +371BA CRC 958EC234 (2509161012) │ │ │ │ +371BE Compressed Size 0000069E (1694) │ │ │ │ +371C2 Uncompressed Size 000011F3 (4595) │ │ │ │ +371C6 Filename Length 001C (28) │ │ │ │ +371C8 Extra Length 001C (28) │ │ │ │ +371CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x371CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +371E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +371E8 Length 0009 (9) │ │ │ │ +371EA Flags 03 (3) 'Modification Access' │ │ │ │ +371EB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +371EF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +371F3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +371F5 Length 000B (11) │ │ │ │ +371F7 Version 01 (1) │ │ │ │ +371F8 UID Size 04 (4) │ │ │ │ +371F9 UID 00000000 (0) │ │ │ │ +371FD GID Size 04 (4) │ │ │ │ +371FE GID 00000000 (0) │ │ │ │ +37202 PAYLOAD │ │ │ │ + │ │ │ │ +378A0 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +378A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +378A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +378A6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +378A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +378AA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +378AE CRC EE3DA990 (3997018512) │ │ │ │ +378B2 Compressed Size 0000107C (4220) │ │ │ │ +378B6 Uncompressed Size 00004BFE (19454) │ │ │ │ +378BA Filename Length 001B (27) │ │ │ │ +378BC Extra Length 001C (28) │ │ │ │ +378BE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x378BE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +378D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +378DB Length 0009 (9) │ │ │ │ +378DD Flags 03 (3) 'Modification Access' │ │ │ │ +378DE Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +378E2 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +378E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +378E8 Length 000B (11) │ │ │ │ +378EA Version 01 (1) │ │ │ │ +378EB UID Size 04 (4) │ │ │ │ +378EC UID 00000000 (0) │ │ │ │ +378F0 GID Size 04 (4) │ │ │ │ +378F1 GID 00000000 (0) │ │ │ │ +378F5 PAYLOAD │ │ │ │ + │ │ │ │ +38971 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +38975 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38976 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38977 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +38979 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3897B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3897F CRC 045E51FF (73290239) │ │ │ │ +38983 Compressed Size 00003B3B (15163) │ │ │ │ +38987 Uncompressed Size 0000D491 (54417) │ │ │ │ +3898B Filename Length 001D (29) │ │ │ │ +3898D Extra Length 001C (28) │ │ │ │ +3898F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3898F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +389AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +389AE Length 0009 (9) │ │ │ │ +389B0 Flags 03 (3) 'Modification Access' │ │ │ │ +389B1 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +389B5 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +389B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +389BB Length 000B (11) │ │ │ │ +389BD Version 01 (1) │ │ │ │ +389BE UID Size 04 (4) │ │ │ │ +389BF UID 00000000 (0) │ │ │ │ +389C3 GID Size 04 (4) │ │ │ │ +389C4 GID 00000000 (0) │ │ │ │ +389C8 PAYLOAD │ │ │ │ + │ │ │ │ +3C503 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +3C507 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3C508 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3C509 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3C50B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3C50D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3C511 CRC C52CC238 (3308044856) │ │ │ │ +3C515 Compressed Size 00000D6A (3434) │ │ │ │ +3C519 Uncompressed Size 0000388A (14474) │ │ │ │ +3C51D Filename Length 001D (29) │ │ │ │ +3C51F Extra Length 001C (28) │ │ │ │ +3C521 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3C521: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C53E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C540 Length 0009 (9) │ │ │ │ +3C542 Flags 03 (3) 'Modification Access' │ │ │ │ +3C543 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3C547 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3C54B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C54D Length 000B (11) │ │ │ │ +3C54F Version 01 (1) │ │ │ │ +3C550 UID Size 04 (4) │ │ │ │ +3C551 UID 00000000 (0) │ │ │ │ +3C555 GID Size 04 (4) │ │ │ │ +3C556 GID 00000000 (0) │ │ │ │ +3C55A PAYLOAD │ │ │ │ + │ │ │ │ +3D2C4 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +3D2C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3D2C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3D2CA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3D2CC Compression Method 0008 (8) 'Deflated' │ │ │ │ +3D2CE Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3D2D2 CRC 0BFFA3F1 (201303025) │ │ │ │ +3D2D6 Compressed Size 00001C89 (7305) │ │ │ │ +3D2DA Uncompressed Size 0000C038 (49208) │ │ │ │ +3D2DE Filename Length 001A (26) │ │ │ │ +3D2E0 Extra Length 001C (28) │ │ │ │ +3D2E2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3D2E2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3D2FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3D2FE Length 0009 (9) │ │ │ │ +3D300 Flags 03 (3) 'Modification Access' │ │ │ │ +3D301 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3D305 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3D309 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3D30B Length 000B (11) │ │ │ │ +3D30D Version 01 (1) │ │ │ │ +3D30E UID Size 04 (4) │ │ │ │ +3D30F UID 00000000 (0) │ │ │ │ +3D313 GID Size 04 (4) │ │ │ │ +3D314 GID 00000000 (0) │ │ │ │ +3D318 PAYLOAD │ │ │ │ + │ │ │ │ +3EFA1 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +3EFA5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3EFA6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3EFA7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3EFA9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3EFAB Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3EFAF CRC 478B6B96 (1200319382) │ │ │ │ +3EFB3 Compressed Size 000003DF (991) │ │ │ │ +3EFB7 Uncompressed Size 00000935 (2357) │ │ │ │ +3EFBB Filename Length 0012 (18) │ │ │ │ +3EFBD Extra Length 001C (28) │ │ │ │ +3EFBF Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3EFBF: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3EFD1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3EFD3 Length 0009 (9) │ │ │ │ +3EFD5 Flags 03 (3) 'Modification Access' │ │ │ │ +3EFD6 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3EFDA Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3EFDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3EFE0 Length 000B (11) │ │ │ │ +3EFE2 Version 01 (1) │ │ │ │ +3EFE3 UID Size 04 (4) │ │ │ │ +3EFE4 UID 00000000 (0) │ │ │ │ +3EFE8 GID Size 04 (4) │ │ │ │ +3EFE9 GID 00000000 (0) │ │ │ │ +3EFED PAYLOAD │ │ │ │ + │ │ │ │ +3F3CC LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +3F3D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3F3D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3F3D2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3F3D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3F3D6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3F3DA CRC E79489F0 (3885271536) │ │ │ │ +3F3DE Compressed Size 000001D3 (467) │ │ │ │ +3F3E2 Uncompressed Size 00000311 (785) │ │ │ │ +3F3E6 Filename Length 0020 (32) │ │ │ │ +3F3E8 Extra Length 001C (28) │ │ │ │ +3F3EA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3F3EA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3F40A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3F40C Length 0009 (9) │ │ │ │ +3F40E Flags 03 (3) 'Modification Access' │ │ │ │ +3F40F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3F413 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3F417 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3F419 Length 000B (11) │ │ │ │ +3F41B Version 01 (1) │ │ │ │ +3F41C UID Size 04 (4) │ │ │ │ +3F41D UID 00000000 (0) │ │ │ │ +3F421 GID Size 04 (4) │ │ │ │ +3F422 GID 00000000 (0) │ │ │ │ +3F426 PAYLOAD │ │ │ │ + │ │ │ │ +3F5F9 LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +3F5FD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3F5FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3F5FF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3F601 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3F603 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +3F607 CRC 73552FFE (1934962686) │ │ │ │ +3F60B Compressed Size 000017AB (6059) │ │ │ │ +3F60F Uncompressed Size 00009D1B (40219) │ │ │ │ +3F613 Filename Length 001B (27) │ │ │ │ +3F615 Extra Length 001C (28) │ │ │ │ +3F617 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3F617: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3F632 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3F634 Length 0009 (9) │ │ │ │ +3F636 Flags 03 (3) 'Modification Access' │ │ │ │ +3F637 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3F63B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +3F63F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3F641 Length 000B (11) │ │ │ │ +3F643 Version 01 (1) │ │ │ │ +3F644 UID Size 04 (4) │ │ │ │ +3F645 UID 00000000 (0) │ │ │ │ +3F649 GID Size 04 (4) │ │ │ │ +3F64A GID 00000000 (0) │ │ │ │ +3F64E PAYLOAD │ │ │ │ + │ │ │ │ +40DF9 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +40DFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +40DFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +40DFF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +40E01 Compression Method 0008 (8) 'Deflated' │ │ │ │ +40E03 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +40E07 CRC 09EB9A96 (166435478) │ │ │ │ +40E0B Compressed Size 0000136D (4973) │ │ │ │ +40E0F Uncompressed Size 00003B58 (15192) │ │ │ │ +40E13 Filename Length 0015 (21) │ │ │ │ +40E15 Extra Length 001C (28) │ │ │ │ +40E17 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x40E17: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40E2C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +40E2E Length 0009 (9) │ │ │ │ +40E30 Flags 03 (3) 'Modification Access' │ │ │ │ +40E31 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +40E35 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +40E39 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40E3B Length 000B (11) │ │ │ │ +40E3D Version 01 (1) │ │ │ │ +40E3E UID Size 04 (4) │ │ │ │ +40E3F UID 00000000 (0) │ │ │ │ +40E43 GID Size 04 (4) │ │ │ │ +40E44 GID 00000000 (0) │ │ │ │ +40E48 PAYLOAD │ │ │ │ + │ │ │ │ +421B5 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +421B9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +421BA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +421BB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +421BD Compression Method 0008 (8) 'Deflated' │ │ │ │ +421BF Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +421C3 CRC F9EDEEDB (4193119963) │ │ │ │ +421C7 Compressed Size 00000AC9 (2761) │ │ │ │ +421CB Uncompressed Size 00002133 (8499) │ │ │ │ +421CF Filename Length 0011 (17) │ │ │ │ +421D1 Extra Length 001C (28) │ │ │ │ +421D3 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x421D3: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +421E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +421E6 Length 0009 (9) │ │ │ │ +421E8 Flags 03 (3) 'Modification Access' │ │ │ │ +421E9 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +421ED Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +421F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +421F3 Length 000B (11) │ │ │ │ +421F5 Version 01 (1) │ │ │ │ +421F6 UID Size 04 (4) │ │ │ │ +421F7 UID 00000000 (0) │ │ │ │ +421FB GID Size 04 (4) │ │ │ │ +421FC GID 00000000 (0) │ │ │ │ +42200 PAYLOAD │ │ │ │ + │ │ │ │ +42CC9 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ +42CCD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +42CCE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +42CCF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +42CD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +42CD3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +42CD7 CRC 78F661F4 (2029412852) │ │ │ │ +42CDB Compressed Size 000003FE (1022) │ │ │ │ +42CDF Uncompressed Size 00000F0C (3852) │ │ │ │ +42CE3 Filename Length 0014 (20) │ │ │ │ +42CE5 Extra Length 001C (28) │ │ │ │ +42CE7 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x42CE7: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +42CFB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +42CFD Length 0009 (9) │ │ │ │ +42CFF Flags 03 (3) 'Modification Access' │ │ │ │ +42D00 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +42D04 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +42D08 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +42D0A Length 000B (11) │ │ │ │ +42D0C Version 01 (1) │ │ │ │ +42D0D UID Size 04 (4) │ │ │ │ +42D0E UID 00000000 (0) │ │ │ │ +42D12 GID Size 04 (4) │ │ │ │ +42D13 GID 00000000 (0) │ │ │ │ +42D17 PAYLOAD │ │ │ │ + │ │ │ │ +43115 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +43119 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4311A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4311B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4311D Compression Method 0008 (8) 'Deflated' │ │ │ │ +4311F Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +43123 CRC 3F5B0F93 (1062932371) │ │ │ │ +43127 Compressed Size 00001260 (4704) │ │ │ │ +4312B Uncompressed Size 0000346B (13419) │ │ │ │ +4312F Filename Length 0014 (20) │ │ │ │ +43131 Extra Length 001C (28) │ │ │ │ +43133 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x43133: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +43147 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +43149 Length 0009 (9) │ │ │ │ +4314B Flags 03 (3) 'Modification Access' │ │ │ │ +4314C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +43150 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +43154 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +43156 Length 000B (11) │ │ │ │ +43158 Version 01 (1) │ │ │ │ +43159 UID Size 04 (4) │ │ │ │ +4315A UID 00000000 (0) │ │ │ │ +4315E GID Size 04 (4) │ │ │ │ +4315F GID 00000000 (0) │ │ │ │ +43163 PAYLOAD │ │ │ │ + │ │ │ │ +443C3 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ +443C7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +443C8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +443C9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +443CB Compression Method 0008 (8) 'Deflated' │ │ │ │ +443CD Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +443D1 CRC 06606451 (106980433) │ │ │ │ +443D5 Compressed Size 00000ACF (2767) │ │ │ │ +443D9 Uncompressed Size 000022FF (8959) │ │ │ │ +443DD Filename Length 001B (27) │ │ │ │ +443DF Extra Length 001C (28) │ │ │ │ +443E1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x443E1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +443FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +443FE Length 0009 (9) │ │ │ │ +44400 Flags 03 (3) 'Modification Access' │ │ │ │ +44401 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +44405 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +44409 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4440B Length 000B (11) │ │ │ │ +4440D Version 01 (1) │ │ │ │ +4440E UID Size 04 (4) │ │ │ │ +4440F UID 00000000 (0) │ │ │ │ +44413 GID Size 04 (4) │ │ │ │ +44414 GID 00000000 (0) │ │ │ │ +44418 PAYLOAD │ │ │ │ + │ │ │ │ +44EE7 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ +44EEB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +44EEC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +44EED General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +44EEF Compression Method 0008 (8) 'Deflated' │ │ │ │ +44EF1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +44EF5 CRC 8B6CCC23 (2339163171) │ │ │ │ +44EF9 Compressed Size 00000C53 (3155) │ │ │ │ +44EFD Uncompressed Size 00002742 (10050) │ │ │ │ +44F01 Filename Length 0013 (19) │ │ │ │ +44F03 Extra Length 001C (28) │ │ │ │ +44F05 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x44F05: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +44F18 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +44F1A Length 0009 (9) │ │ │ │ +44F1C Flags 03 (3) 'Modification Access' │ │ │ │ +44F1D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +44F21 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +44F25 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +44F27 Length 000B (11) │ │ │ │ +44F29 Version 01 (1) │ │ │ │ +44F2A UID Size 04 (4) │ │ │ │ +44F2B UID 00000000 (0) │ │ │ │ +44F2F GID Size 04 (4) │ │ │ │ +44F30 GID 00000000 (0) │ │ │ │ +44F34 PAYLOAD │ │ │ │ + │ │ │ │ +45B87 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +45B8B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +45B8C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +45B8D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45B8F Compression Method 0008 (8) 'Deflated' │ │ │ │ +45B91 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +45B95 CRC 75D0E296 (1976623766) │ │ │ │ +45B99 Compressed Size 00000C92 (3218) │ │ │ │ +45B9D Uncompressed Size 00003D11 (15633) │ │ │ │ +45BA1 Filename Length 0014 (20) │ │ │ │ +45BA3 Extra Length 001C (28) │ │ │ │ +45BA5 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x45BA5: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +45BB9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45BBB Length 0009 (9) │ │ │ │ +45BBD Flags 03 (3) 'Modification Access' │ │ │ │ +45BBE Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +45BC2 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +45BC6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +45BC8 Length 000B (11) │ │ │ │ +45BCA Version 01 (1) │ │ │ │ +45BCB UID Size 04 (4) │ │ │ │ +45BCC UID 00000000 (0) │ │ │ │ +45BD0 GID Size 04 (4) │ │ │ │ +45BD1 GID 00000000 (0) │ │ │ │ +45BD5 PAYLOAD │ │ │ │ + │ │ │ │ +46867 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +4686B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4686C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4686D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4686F Compression Method 0008 (8) 'Deflated' │ │ │ │ +46871 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +46875 CRC C3A581AC (3282403756) │ │ │ │ +46879 Compressed Size 00000F45 (3909) │ │ │ │ +4687D Uncompressed Size 00003744 (14148) │ │ │ │ +46881 Filename Length 000F (15) │ │ │ │ +46883 Extra Length 001C (28) │ │ │ │ +46885 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x46885: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +46894 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +46896 Length 0009 (9) │ │ │ │ +46898 Flags 03 (3) 'Modification Access' │ │ │ │ +46899 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4689D Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +468A1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +468A3 Length 000B (11) │ │ │ │ +468A5 Version 01 (1) │ │ │ │ +468A6 UID Size 04 (4) │ │ │ │ +468A7 UID 00000000 (0) │ │ │ │ +468AB GID Size 04 (4) │ │ │ │ +468AC GID 00000000 (0) │ │ │ │ +468B0 PAYLOAD │ │ │ │ + │ │ │ │ +477F5 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +477F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +477FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +477FB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +477FD Compression Method 0008 (8) 'Deflated' │ │ │ │ +477FF Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +47803 CRC AD08D6E7 (2903037671) │ │ │ │ +47807 Compressed Size 000006CE (1742) │ │ │ │ +4780B Uncompressed Size 00001AC4 (6852) │ │ │ │ +4780F Filename Length 000F (15) │ │ │ │ +47811 Extra Length 001C (28) │ │ │ │ +47813 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x47813: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +47822 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +47824 Length 0009 (9) │ │ │ │ +47826 Flags 03 (3) 'Modification Access' │ │ │ │ +47827 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4782B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4782F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +47831 Length 000B (11) │ │ │ │ +47833 Version 01 (1) │ │ │ │ +47834 UID Size 04 (4) │ │ │ │ +47835 UID 00000000 (0) │ │ │ │ +47839 GID Size 04 (4) │ │ │ │ +4783A GID 00000000 (0) │ │ │ │ +4783E PAYLOAD │ │ │ │ + │ │ │ │ +47F0C LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +47F10 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +47F11 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +47F12 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +47F14 Compression Method 0008 (8) 'Deflated' │ │ │ │ +47F16 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +47F1A CRC 5583AB14 (1434692372) │ │ │ │ +47F1E Compressed Size 00001A52 (6738) │ │ │ │ +47F22 Uncompressed Size 0000650E (25870) │ │ │ │ +47F26 Filename Length 0013 (19) │ │ │ │ +47F28 Extra Length 001C (28) │ │ │ │ +47F2A Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x47F2A: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +47F3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +47F3F Length 0009 (9) │ │ │ │ +47F41 Flags 03 (3) 'Modification Access' │ │ │ │ +47F42 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +47F46 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +47F4A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +47F4C Length 000B (11) │ │ │ │ +47F4E Version 01 (1) │ │ │ │ +47F4F UID Size 04 (4) │ │ │ │ +47F50 UID 00000000 (0) │ │ │ │ +47F54 GID Size 04 (4) │ │ │ │ +47F55 GID 00000000 (0) │ │ │ │ +47F59 PAYLOAD │ │ │ │ + │ │ │ │ +499AB LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +499AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +499B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +499B1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +499B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +499B5 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +499B9 CRC 90967BFA (2425781242) │ │ │ │ +499BD Compressed Size 000009A6 (2470) │ │ │ │ +499C1 Uncompressed Size 00001B6A (7018) │ │ │ │ +499C5 Filename Length 0010 (16) │ │ │ │ +499C7 Extra Length 001C (28) │ │ │ │ +499C9 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x499C9: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +499D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +499DB Length 0009 (9) │ │ │ │ +499DD Flags 03 (3) 'Modification Access' │ │ │ │ +499DE Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +499E2 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +499E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +499E8 Length 000B (11) │ │ │ │ +499EA Version 01 (1) │ │ │ │ +499EB UID Size 04 (4) │ │ │ │ +499EC UID 00000000 (0) │ │ │ │ +499F0 GID Size 04 (4) │ │ │ │ +499F1 GID 00000000 (0) │ │ │ │ +499F5 PAYLOAD │ │ │ │ + │ │ │ │ +4A39B LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +4A39F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4A3A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4A3A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4A3A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4A3A5 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +4A3A9 CRC D37ED789 (3548305289) │ │ │ │ +4A3AD Compressed Size 000006B6 (1718) │ │ │ │ +4A3B1 Uncompressed Size 00001565 (5477) │ │ │ │ +4A3B5 Filename Length 0012 (18) │ │ │ │ +4A3B7 Extra Length 001C (28) │ │ │ │ +4A3B9 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4A3B9: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4A3CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4A3CD Length 0009 (9) │ │ │ │ +4A3CF Flags 03 (3) 'Modification Access' │ │ │ │ +4A3D0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4A3D4 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4A3D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4A3DA Length 000B (11) │ │ │ │ +4A3DC Version 01 (1) │ │ │ │ +4A3DD UID Size 04 (4) │ │ │ │ +4A3DE UID 00000000 (0) │ │ │ │ +4A3E2 GID Size 04 (4) │ │ │ │ +4A3E3 GID 00000000 (0) │ │ │ │ +4A3E7 PAYLOAD │ │ │ │ + │ │ │ │ +4AA9D LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +4AAA1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4AAA2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4AAA3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4AAA5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4AAA7 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +4AAAB CRC F24D9270 (4065170032) │ │ │ │ +4AAAF Compressed Size 00002D56 (11606) │ │ │ │ +4AAB3 Uncompressed Size 0000D083 (53379) │ │ │ │ +4AAB7 Filename Length 0010 (16) │ │ │ │ +4AAB9 Extra Length 001C (28) │ │ │ │ +4AABB Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4AABB: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4AACB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4AACD Length 0009 (9) │ │ │ │ +4AACF Flags 03 (3) 'Modification Access' │ │ │ │ +4AAD0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4AAD4 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4AAD8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4AADA Length 000B (11) │ │ │ │ +4AADC Version 01 (1) │ │ │ │ +4AADD UID Size 04 (4) │ │ │ │ +4AADE UID 00000000 (0) │ │ │ │ +4AAE2 GID Size 04 (4) │ │ │ │ +4AAE3 GID 00000000 (0) │ │ │ │ +4AAE7 PAYLOAD │ │ │ │ + │ │ │ │ +4D83D LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +4D841 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4D842 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4D843 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4D845 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4D847 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +4D84B CRC 6DD28D49 (1842515273) │ │ │ │ +4D84F Compressed Size 00001E7F (7807) │ │ │ │ +4D853 Uncompressed Size 00009AAA (39594) │ │ │ │ +4D857 Filename Length 0012 (18) │ │ │ │ +4D859 Extra Length 001C (28) │ │ │ │ +4D85B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4D85B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4D86D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4D86F Length 0009 (9) │ │ │ │ +4D871 Flags 03 (3) 'Modification Access' │ │ │ │ +4D872 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4D876 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4D87A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4D87C Length 000B (11) │ │ │ │ +4D87E Version 01 (1) │ │ │ │ +4D87F UID Size 04 (4) │ │ │ │ +4D880 UID 00000000 (0) │ │ │ │ +4D884 GID Size 04 (4) │ │ │ │ +4D885 GID 00000000 (0) │ │ │ │ +4D889 PAYLOAD │ │ │ │ + │ │ │ │ +4F708 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +4F70C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F70D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F70E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F710 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F712 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +4F716 CRC 68D3A344 (1758700356) │ │ │ │ +4F71A Compressed Size 00001478 (5240) │ │ │ │ +4F71E Uncompressed Size 00007AD0 (31440) │ │ │ │ +4F722 Filename Length 0018 (24) │ │ │ │ +4F724 Extra Length 001C (28) │ │ │ │ +4F726 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F726: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F73E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F740 Length 0009 (9) │ │ │ │ +4F742 Flags 03 (3) 'Modification Access' │ │ │ │ +4F743 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4F747 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +4F74B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F74D Length 000B (11) │ │ │ │ +4F74F Version 01 (1) │ │ │ │ +4F750 UID Size 04 (4) │ │ │ │ +4F751 UID 00000000 (0) │ │ │ │ +4F755 GID Size 04 (4) │ │ │ │ +4F756 GID 00000000 (0) │ │ │ │ +4F75A PAYLOAD │ │ │ │ + │ │ │ │ +50BD2 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +50BD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +50BD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +50BD8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +50BDA Compression Method 0008 (8) 'Deflated' │ │ │ │ +50BDC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +50BE0 CRC BE84805E (3196354654) │ │ │ │ +50BE4 Compressed Size 000021E3 (8675) │ │ │ │ +50BE8 Uncompressed Size 0000D21D (53789) │ │ │ │ +50BEC Filename Length 001F (31) │ │ │ │ +50BEE Extra Length 001C (28) │ │ │ │ +50BF0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x50BF0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +50C0F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +50C11 Length 0009 (9) │ │ │ │ +50C13 Flags 03 (3) 'Modification Access' │ │ │ │ +50C14 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +50C18 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +50C1C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +50C1E Length 000B (11) │ │ │ │ +50C20 Version 01 (1) │ │ │ │ +50C21 UID Size 04 (4) │ │ │ │ +50C22 UID 00000000 (0) │ │ │ │ +50C26 GID Size 04 (4) │ │ │ │ +50C27 GID 00000000 (0) │ │ │ │ +50C2B PAYLOAD │ │ │ │ + │ │ │ │ +52E0E LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +52E12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +52E13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +52E14 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +52E16 Compression Method 0008 (8) 'Deflated' │ │ │ │ +52E18 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +52E1C CRC 585AC976 (1482344822) │ │ │ │ +52E20 Compressed Size 000003F7 (1015) │ │ │ │ +52E24 Uncompressed Size 000008A3 (2211) │ │ │ │ +52E28 Filename Length 001E (30) │ │ │ │ +52E2A Extra Length 001C (28) │ │ │ │ +52E2C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x52E2C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +52E4A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +52E4C Length 0009 (9) │ │ │ │ +52E4E Flags 03 (3) 'Modification Access' │ │ │ │ +52E4F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +52E53 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +52E57 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +52E59 Length 000B (11) │ │ │ │ +52E5B Version 01 (1) │ │ │ │ +52E5C UID Size 04 (4) │ │ │ │ +52E5D UID 00000000 (0) │ │ │ │ +52E61 GID Size 04 (4) │ │ │ │ +52E62 GID 00000000 (0) │ │ │ │ +52E66 PAYLOAD │ │ │ │ + │ │ │ │ +5325D LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +53261 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +53262 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +53263 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +53265 Compression Method 0008 (8) 'Deflated' │ │ │ │ +53267 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5326B CRC 16308EDA (372281050) │ │ │ │ +5326F Compressed Size 00004361 (17249) │ │ │ │ +53273 Uncompressed Size 0000E06F (57455) │ │ │ │ +53277 Filename Length 0013 (19) │ │ │ │ +53279 Extra Length 001C (28) │ │ │ │ +5327B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5327B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5328E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +53290 Length 0009 (9) │ │ │ │ +53292 Flags 03 (3) 'Modification Access' │ │ │ │ +53293 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +53297 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5329B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5329D Length 000B (11) │ │ │ │ +5329F Version 01 (1) │ │ │ │ +532A0 UID Size 04 (4) │ │ │ │ +532A1 UID 00000000 (0) │ │ │ │ +532A5 GID Size 04 (4) │ │ │ │ +532A6 GID 00000000 (0) │ │ │ │ +532AA PAYLOAD │ │ │ │ + │ │ │ │ +5760B LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +5760F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +57610 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +57611 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +57613 Compression Method 0008 (8) 'Deflated' │ │ │ │ +57615 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +57619 CRC 4B98C7C0 (1268303808) │ │ │ │ +5761D Compressed Size 000026C3 (9923) │ │ │ │ +57621 Uncompressed Size 00006E45 (28229) │ │ │ │ +57625 Filename Length 0019 (25) │ │ │ │ +57627 Extra Length 001C (28) │ │ │ │ +57629 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x57629: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +57642 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +57644 Length 0009 (9) │ │ │ │ +57646 Flags 03 (3) 'Modification Access' │ │ │ │ +57647 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5764B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5764F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +57651 Length 000B (11) │ │ │ │ +57653 Version 01 (1) │ │ │ │ +57654 UID Size 04 (4) │ │ │ │ +57655 UID 00000000 (0) │ │ │ │ +57659 GID Size 04 (4) │ │ │ │ +5765A GID 00000000 (0) │ │ │ │ +5765E PAYLOAD │ │ │ │ + │ │ │ │ +59D21 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +59D25 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +59D26 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +59D27 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +59D29 Compression Method 0008 (8) 'Deflated' │ │ │ │ +59D2B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +59D2F CRC F8618863 (4167141475) │ │ │ │ +59D33 Compressed Size 00002738 (10040) │ │ │ │ +59D37 Uncompressed Size 00008B83 (35715) │ │ │ │ +59D3B Filename Length 0019 (25) │ │ │ │ +59D3D Extra Length 001C (28) │ │ │ │ +59D3F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x59D3F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +59D58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +59D5A Length 0009 (9) │ │ │ │ +59D5C Flags 03 (3) 'Modification Access' │ │ │ │ +59D5D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +59D61 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +59D65 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +59D67 Length 000B (11) │ │ │ │ +59D69 Version 01 (1) │ │ │ │ +59D6A UID Size 04 (4) │ │ │ │ +59D6B UID 00000000 (0) │ │ │ │ +59D6F GID Size 04 (4) │ │ │ │ +59D70 GID 00000000 (0) │ │ │ │ +59D74 PAYLOAD │ │ │ │ + │ │ │ │ +5C4AC LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +5C4B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5C4B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5C4B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5C4B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +5C4B6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5C4BA CRC 19093DDC (420036060) │ │ │ │ +5C4BE Compressed Size 00000ECC (3788) │ │ │ │ +5C4C2 Uncompressed Size 000053BF (21439) │ │ │ │ +5C4C6 Filename Length 0021 (33) │ │ │ │ +5C4C8 Extra Length 001C (28) │ │ │ │ +5C4CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5C4CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5C4EB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5C4ED Length 0009 (9) │ │ │ │ +5C4EF Flags 03 (3) 'Modification Access' │ │ │ │ +5C4F0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5C4F4 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5C4F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5C4FA Length 000B (11) │ │ │ │ +5C4FC Version 01 (1) │ │ │ │ +5C4FD UID Size 04 (4) │ │ │ │ +5C4FE UID 00000000 (0) │ │ │ │ +5C502 GID Size 04 (4) │ │ │ │ +5C503 GID 00000000 (0) │ │ │ │ +5C507 PAYLOAD │ │ │ │ + │ │ │ │ +5D3D3 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +5D3D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5D3D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5D3D9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5D3DB Compression Method 0008 (8) 'Deflated' │ │ │ │ +5D3DD Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5D3E1 CRC D1EC3113 (3521917203) │ │ │ │ +5D3E5 Compressed Size 00000535 (1333) │ │ │ │ +5D3E9 Uncompressed Size 00000C96 (3222) │ │ │ │ +5D3ED Filename Length 0017 (23) │ │ │ │ +5D3EF Extra Length 001C (28) │ │ │ │ +5D3F1 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5D3F1: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5D408 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5D40A Length 0009 (9) │ │ │ │ +5D40C Flags 03 (3) 'Modification Access' │ │ │ │ +5D40D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5D411 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5D415 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5D417 Length 000B (11) │ │ │ │ +5D419 Version 01 (1) │ │ │ │ +5D41A UID Size 04 (4) │ │ │ │ +5D41B UID 00000000 (0) │ │ │ │ +5D41F GID Size 04 (4) │ │ │ │ +5D420 GID 00000000 (0) │ │ │ │ +5D424 PAYLOAD │ │ │ │ + │ │ │ │ +5D959 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +5D95D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5D95E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5D95F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5D961 Compression Method 0008 (8) 'Deflated' │ │ │ │ +5D963 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5D967 CRC 9ED6F2D9 (2664887001) │ │ │ │ +5D96B Compressed Size 00000467 (1127) │ │ │ │ +5D96F Uncompressed Size 00000931 (2353) │ │ │ │ +5D973 Filename Length 001B (27) │ │ │ │ +5D975 Extra Length 001C (28) │ │ │ │ +5D977 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5D977: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5D992 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5D994 Length 0009 (9) │ │ │ │ +5D996 Flags 03 (3) 'Modification Access' │ │ │ │ +5D997 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5D99B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5D99F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5D9A1 Length 000B (11) │ │ │ │ +5D9A3 Version 01 (1) │ │ │ │ +5D9A4 UID Size 04 (4) │ │ │ │ +5D9A5 UID 00000000 (0) │ │ │ │ +5D9A9 GID Size 04 (4) │ │ │ │ +5D9AA GID 00000000 (0) │ │ │ │ +5D9AE PAYLOAD │ │ │ │ + │ │ │ │ +5DE15 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +5DE19 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5DE1A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5DE1B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5DE1D Compression Method 0008 (8) 'Deflated' │ │ │ │ +5DE1F Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5DE23 CRC 2606F569 (637990249) │ │ │ │ +5DE27 Compressed Size 000016F6 (5878) │ │ │ │ +5DE2B Uncompressed Size 00007A86 (31366) │ │ │ │ +5DE2F Filename Length 001F (31) │ │ │ │ +5DE31 Extra Length 001C (28) │ │ │ │ +5DE33 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5DE33: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5DE52 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5DE54 Length 0009 (9) │ │ │ │ +5DE56 Flags 03 (3) 'Modification Access' │ │ │ │ +5DE57 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5DE5B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5DE5F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5DE61 Length 000B (11) │ │ │ │ +5DE63 Version 01 (1) │ │ │ │ +5DE64 UID Size 04 (4) │ │ │ │ +5DE65 UID 00000000 (0) │ │ │ │ +5DE69 GID Size 04 (4) │ │ │ │ +5DE6A GID 00000000 (0) │ │ │ │ +5DE6E PAYLOAD │ │ │ │ + │ │ │ │ +5F564 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +5F568 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5F569 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5F56A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5F56C Compression Method 0008 (8) 'Deflated' │ │ │ │ +5F56E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +5F572 CRC 758BB33D (1972089661) │ │ │ │ +5F576 Compressed Size 00004168 (16744) │ │ │ │ +5F57A Uncompressed Size 0001D163 (119139) │ │ │ │ +5F57E Filename Length 0010 (16) │ │ │ │ +5F580 Extra Length 001C (28) │ │ │ │ +5F582 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5F582: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5F592 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5F594 Length 0009 (9) │ │ │ │ +5F596 Flags 03 (3) 'Modification Access' │ │ │ │ +5F597 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5F59B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +5F59F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5F5A1 Length 000B (11) │ │ │ │ +5F5A3 Version 01 (1) │ │ │ │ +5F5A4 UID Size 04 (4) │ │ │ │ +5F5A5 UID 00000000 (0) │ │ │ │ +5F5A9 GID Size 04 (4) │ │ │ │ +5F5AA GID 00000000 (0) │ │ │ │ +5F5AE PAYLOAD │ │ │ │ + │ │ │ │ +63716 LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +6371A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6371B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6371C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6371E Compression Method 0008 (8) 'Deflated' │ │ │ │ +63720 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +63724 CRC C55EF92D (3311335725) │ │ │ │ +63728 Compressed Size 00000AE8 (2792) │ │ │ │ +6372C Uncompressed Size 000021E8 (8680) │ │ │ │ +63730 Filename Length 0014 (20) │ │ │ │ +63732 Extra Length 001C (28) │ │ │ │ +63734 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x63734: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63748 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6374A Length 0009 (9) │ │ │ │ +6374C Flags 03 (3) 'Modification Access' │ │ │ │ +6374D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +63751 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +63755 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +63757 Length 000B (11) │ │ │ │ +63759 Version 01 (1) │ │ │ │ +6375A UID Size 04 (4) │ │ │ │ +6375B UID 00000000 (0) │ │ │ │ +6375F GID Size 04 (4) │ │ │ │ +63760 GID 00000000 (0) │ │ │ │ +63764 PAYLOAD │ │ │ │ + │ │ │ │ +6424C LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +64250 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +64251 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +64252 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +64254 Compression Method 0008 (8) 'Deflated' │ │ │ │ +64256 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +6425A CRC 32543857 (844380247) │ │ │ │ +6425E Compressed Size 0000B523 (46371) │ │ │ │ +64262 Uncompressed Size 00041755 (268117) │ │ │ │ +64266 Filename Length 0017 (23) │ │ │ │ +64268 Extra Length 001C (28) │ │ │ │ +6426A Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6426A: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +64281 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +64283 Length 0009 (9) │ │ │ │ +64285 Flags 03 (3) 'Modification Access' │ │ │ │ +64286 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6428A Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6428E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +64290 Length 000B (11) │ │ │ │ +64292 Version 01 (1) │ │ │ │ +64293 UID Size 04 (4) │ │ │ │ +64294 UID 00000000 (0) │ │ │ │ +64298 GID Size 04 (4) │ │ │ │ +64299 GID 00000000 (0) │ │ │ │ +6429D PAYLOAD │ │ │ │ + │ │ │ │ +6F7C0 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +6F7C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6F7C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6F7C6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6F7C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6F7CA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +6F7CE CRC 564036C9 (1447048905) │ │ │ │ +6F7D2 Compressed Size 00000400 (1024) │ │ │ │ +6F7D6 Uncompressed Size 0000093D (2365) │ │ │ │ +6F7DA Filename Length 0013 (19) │ │ │ │ +6F7DC Extra Length 001C (28) │ │ │ │ +6F7DE Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6F7DE: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6F7F1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6F7F3 Length 0009 (9) │ │ │ │ +6F7F5 Flags 03 (3) 'Modification Access' │ │ │ │ +6F7F6 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6F7FA Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6F7FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6F800 Length 000B (11) │ │ │ │ +6F802 Version 01 (1) │ │ │ │ +6F803 UID Size 04 (4) │ │ │ │ +6F804 UID 00000000 (0) │ │ │ │ +6F808 GID Size 04 (4) │ │ │ │ +6F809 GID 00000000 (0) │ │ │ │ +6F80D PAYLOAD │ │ │ │ + │ │ │ │ +6FC0D LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +6FC11 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6FC12 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6FC13 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6FC15 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6FC17 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +6FC1B CRC 4AD4846E (1255441518) │ │ │ │ +6FC1F Compressed Size 000014D9 (5337) │ │ │ │ +6FC23 Uncompressed Size 00006892 (26770) │ │ │ │ +6FC27 Filename Length 0012 (18) │ │ │ │ +6FC29 Extra Length 001C (28) │ │ │ │ +6FC2B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6FC2B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6FC3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6FC3F Length 0009 (9) │ │ │ │ +6FC41 Flags 03 (3) 'Modification Access' │ │ │ │ +6FC42 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6FC46 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +6FC4A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6FC4C Length 000B (11) │ │ │ │ +6FC4E Version 01 (1) │ │ │ │ +6FC4F UID Size 04 (4) │ │ │ │ +6FC50 UID 00000000 (0) │ │ │ │ +6FC54 GID Size 04 (4) │ │ │ │ +6FC55 GID 00000000 (0) │ │ │ │ +6FC59 PAYLOAD │ │ │ │ + │ │ │ │ +71132 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +71136 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +71137 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +71138 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7113A Compression Method 0008 (8) 'Deflated' │ │ │ │ +7113C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +71140 CRC AB6E6BBC (2876140476) │ │ │ │ +71144 Compressed Size 00001205 (4613) │ │ │ │ +71148 Uncompressed Size 0000414F (16719) │ │ │ │ +7114C Filename Length 0012 (18) │ │ │ │ +7114E Extra Length 001C (28) │ │ │ │ +71150 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x71150: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +71162 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +71164 Length 0009 (9) │ │ │ │ +71166 Flags 03 (3) 'Modification Access' │ │ │ │ +71167 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7116B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7116F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +71171 Length 000B (11) │ │ │ │ +71173 Version 01 (1) │ │ │ │ +71174 UID Size 04 (4) │ │ │ │ +71175 UID 00000000 (0) │ │ │ │ +71179 GID Size 04 (4) │ │ │ │ +7117A GID 00000000 (0) │ │ │ │ +7117E PAYLOAD │ │ │ │ + │ │ │ │ +72383 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +72387 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +72388 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +72389 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7238B Compression Method 0008 (8) 'Deflated' │ │ │ │ +7238D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +72391 CRC 5C8A4BDC (1552567260) │ │ │ │ +72395 Compressed Size 00000704 (1796) │ │ │ │ +72399 Uncompressed Size 000011A7 (4519) │ │ │ │ +7239D Filename Length 0019 (25) │ │ │ │ +7239F Extra Length 001C (28) │ │ │ │ +723A1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x723A1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +723BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +723BC Length 0009 (9) │ │ │ │ +723BE Flags 03 (3) 'Modification Access' │ │ │ │ +723BF Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +723C3 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +723C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +723C9 Length 000B (11) │ │ │ │ +723CB Version 01 (1) │ │ │ │ +723CC UID Size 04 (4) │ │ │ │ +723CD UID 00000000 (0) │ │ │ │ +723D1 GID Size 04 (4) │ │ │ │ +723D2 GID 00000000 (0) │ │ │ │ +723D6 PAYLOAD │ │ │ │ + │ │ │ │ +72ADA LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +72ADE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +72ADF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +72AE0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +72AE2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +72AE4 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +72AE8 CRC 3381C721 (864143137) │ │ │ │ +72AEC Compressed Size 000018B5 (6325) │ │ │ │ +72AF0 Uncompressed Size 0000A678 (42616) │ │ │ │ +72AF4 Filename Length 0019 (25) │ │ │ │ +72AF6 Extra Length 001C (28) │ │ │ │ +72AF8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x72AF8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +72B11 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +72B13 Length 0009 (9) │ │ │ │ +72B15 Flags 03 (3) 'Modification Access' │ │ │ │ +72B16 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +72B1A Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +72B1E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +72B20 Length 000B (11) │ │ │ │ +72B22 Version 01 (1) │ │ │ │ +72B23 UID Size 04 (4) │ │ │ │ +72B24 UID 00000000 (0) │ │ │ │ +72B28 GID Size 04 (4) │ │ │ │ +72B29 GID 00000000 (0) │ │ │ │ +72B2D PAYLOAD │ │ │ │ + │ │ │ │ +743E2 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +743E6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +743E7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +743E8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +743EA Compression Method 0008 (8) 'Deflated' │ │ │ │ +743EC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +743F0 CRC 4D1409E6 (1293158886) │ │ │ │ +743F4 Compressed Size 0000177C (6012) │ │ │ │ +743F8 Uncompressed Size 0000472C (18220) │ │ │ │ +743FC Filename Length 0014 (20) │ │ │ │ +743FE Extra Length 001C (28) │ │ │ │ +74400 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x74400: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +74414 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +74416 Length 0009 (9) │ │ │ │ +74418 Flags 03 (3) 'Modification Access' │ │ │ │ +74419 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7441D Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +74421 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +74423 Length 000B (11) │ │ │ │ +74425 Version 01 (1) │ │ │ │ +74426 UID Size 04 (4) │ │ │ │ +74427 UID 00000000 (0) │ │ │ │ +7442B GID Size 04 (4) │ │ │ │ +7442C GID 00000000 (0) │ │ │ │ +74430 PAYLOAD │ │ │ │ + │ │ │ │ +75BAC LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +75BB0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +75BB1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +75BB2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +75BB4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +75BB6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +75BBA CRC 2F723DFF (796016127) │ │ │ │ +75BBE Compressed Size 00000409 (1033) │ │ │ │ +75BC2 Uncompressed Size 00000825 (2085) │ │ │ │ +75BC6 Filename Length 001C (28) │ │ │ │ +75BC8 Extra Length 001C (28) │ │ │ │ +75BCA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x75BCA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +75BE6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +75BE8 Length 0009 (9) │ │ │ │ +75BEA Flags 03 (3) 'Modification Access' │ │ │ │ +75BEB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +75BEF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +75BF3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +75BF5 Length 000B (11) │ │ │ │ +75BF7 Version 01 (1) │ │ │ │ +75BF8 UID Size 04 (4) │ │ │ │ +75BF9 UID 00000000 (0) │ │ │ │ +75BFD GID Size 04 (4) │ │ │ │ +75BFE GID 00000000 (0) │ │ │ │ +75C02 PAYLOAD │ │ │ │ + │ │ │ │ +7600B LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +7600F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +76010 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +76011 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +76013 Compression Method 0008 (8) 'Deflated' │ │ │ │ +76015 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +76019 CRC AF61C4DB (2942420187) │ │ │ │ +7601D Compressed Size 000024C3 (9411) │ │ │ │ +76021 Uncompressed Size 0000B65D (46685) │ │ │ │ +76025 Filename Length 001F (31) │ │ │ │ +76027 Extra Length 001C (28) │ │ │ │ +76029 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x76029: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +76048 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7604A Length 0009 (9) │ │ │ │ +7604C Flags 03 (3) 'Modification Access' │ │ │ │ +7604D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +76051 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +76055 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +76057 Length 000B (11) │ │ │ │ +76059 Version 01 (1) │ │ │ │ +7605A UID Size 04 (4) │ │ │ │ +7605B UID 00000000 (0) │ │ │ │ +7605F GID Size 04 (4) │ │ │ │ +76060 GID 00000000 (0) │ │ │ │ +76064 PAYLOAD │ │ │ │ + │ │ │ │ +78527 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +7852B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7852C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7852D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7852F Compression Method 0008 (8) 'Deflated' │ │ │ │ +78531 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +78535 CRC BDFBBF8D (3187392397) │ │ │ │ +78539 Compressed Size 00000E7A (3706) │ │ │ │ +7853D Uncompressed Size 000052DA (21210) │ │ │ │ +78541 Filename Length 001F (31) │ │ │ │ +78543 Extra Length 001C (28) │ │ │ │ +78545 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x78545: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +78564 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +78566 Length 0009 (9) │ │ │ │ +78568 Flags 03 (3) 'Modification Access' │ │ │ │ +78569 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7856D Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +78571 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +78573 Length 000B (11) │ │ │ │ +78575 Version 01 (1) │ │ │ │ +78576 UID Size 04 (4) │ │ │ │ +78577 UID 00000000 (0) │ │ │ │ +7857B GID Size 04 (4) │ │ │ │ +7857C GID 00000000 (0) │ │ │ │ +78580 PAYLOAD │ │ │ │ + │ │ │ │ +793FA LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +793FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +793FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +79400 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +79402 Compression Method 0008 (8) 'Deflated' │ │ │ │ +79404 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +79408 CRC 720DE003 (1913511939) │ │ │ │ +7940C Compressed Size 00000A45 (2629) │ │ │ │ +79410 Uncompressed Size 0000247A (9338) │ │ │ │ +79414 Filename Length 0013 (19) │ │ │ │ +79416 Extra Length 001C (28) │ │ │ │ +79418 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x79418: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7942B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7942D Length 0009 (9) │ │ │ │ +7942F Flags 03 (3) 'Modification Access' │ │ │ │ +79430 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +79434 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +79438 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7943A Length 000B (11) │ │ │ │ +7943C Version 01 (1) │ │ │ │ +7943D UID Size 04 (4) │ │ │ │ +7943E UID 00000000 (0) │ │ │ │ +79442 GID Size 04 (4) │ │ │ │ +79443 GID 00000000 (0) │ │ │ │ +79447 PAYLOAD │ │ │ │ + │ │ │ │ +79E8C LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +79E90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +79E91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +79E92 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +79E94 Compression Method 0008 (8) 'Deflated' │ │ │ │ +79E96 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +79E9A CRC 2F406ADE (792750814) │ │ │ │ +79E9E Compressed Size 0000258D (9613) │ │ │ │ +79EA2 Uncompressed Size 0000BAA4 (47780) │ │ │ │ +79EA6 Filename Length 0019 (25) │ │ │ │ +79EA8 Extra Length 001C (28) │ │ │ │ +79EAA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x79EAA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +79EC3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +79EC5 Length 0009 (9) │ │ │ │ +79EC7 Flags 03 (3) 'Modification Access' │ │ │ │ +79EC8 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +79ECC Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +79ED0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +79ED2 Length 000B (11) │ │ │ │ +79ED4 Version 01 (1) │ │ │ │ +79ED5 UID Size 04 (4) │ │ │ │ +79ED6 UID 00000000 (0) │ │ │ │ +79EDA GID Size 04 (4) │ │ │ │ +79EDB GID 00000000 (0) │ │ │ │ +79EDF PAYLOAD │ │ │ │ + │ │ │ │ +7C46C LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +7C470 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7C471 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7C472 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7C474 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7C476 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +7C47A CRC E061E754 (3764512596) │ │ │ │ +7C47E Compressed Size 00000EFD (3837) │ │ │ │ +7C482 Uncompressed Size 00003A2F (14895) │ │ │ │ +7C486 Filename Length 0024 (36) │ │ │ │ +7C488 Extra Length 001C (28) │ │ │ │ +7C48A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7C48A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7C4AE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7C4B0 Length 0009 (9) │ │ │ │ +7C4B2 Flags 03 (3) 'Modification Access' │ │ │ │ +7C4B3 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7C4B7 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7C4BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7C4BD Length 000B (11) │ │ │ │ +7C4BF Version 01 (1) │ │ │ │ +7C4C0 UID Size 04 (4) │ │ │ │ +7C4C1 UID 00000000 (0) │ │ │ │ +7C4C5 GID Size 04 (4) │ │ │ │ +7C4C6 GID 00000000 (0) │ │ │ │ +7C4CA PAYLOAD │ │ │ │ + │ │ │ │ +7D3C7 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +7D3CB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7D3CC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7D3CD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7D3CF Compression Method 0008 (8) 'Deflated' │ │ │ │ +7D3D1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +7D3D5 CRC EC4F7E41 (3964632641) │ │ │ │ +7D3D9 Compressed Size 00001AE9 (6889) │ │ │ │ +7D3DD Uncompressed Size 00005F7F (24447) │ │ │ │ +7D3E1 Filename Length 0017 (23) │ │ │ │ +7D3E3 Extra Length 001C (28) │ │ │ │ +7D3E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7D3E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7D3FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7D3FE Length 0009 (9) │ │ │ │ +7D400 Flags 03 (3) 'Modification Access' │ │ │ │ +7D401 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7D405 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7D409 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7D40B Length 000B (11) │ │ │ │ +7D40D Version 01 (1) │ │ │ │ +7D40E UID Size 04 (4) │ │ │ │ +7D40F UID 00000000 (0) │ │ │ │ +7D413 GID Size 04 (4) │ │ │ │ +7D414 GID 00000000 (0) │ │ │ │ +7D418 PAYLOAD │ │ │ │ + │ │ │ │ +7EF01 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +7EF05 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7EF06 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7EF07 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7EF09 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7EF0B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +7EF0F CRC 11E32AF1 (300100337) │ │ │ │ +7EF13 Compressed Size 00000ED3 (3795) │ │ │ │ +7EF17 Uncompressed Size 000038E2 (14562) │ │ │ │ +7EF1B Filename Length 0023 (35) │ │ │ │ +7EF1D Extra Length 001C (28) │ │ │ │ +7EF1F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7EF1F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7EF42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7EF44 Length 0009 (9) │ │ │ │ +7EF46 Flags 03 (3) 'Modification Access' │ │ │ │ +7EF47 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7EF4B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7EF4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7EF51 Length 000B (11) │ │ │ │ +7EF53 Version 01 (1) │ │ │ │ +7EF54 UID Size 04 (4) │ │ │ │ +7EF55 UID 00000000 (0) │ │ │ │ +7EF59 GID Size 04 (4) │ │ │ │ +7EF5A GID 00000000 (0) │ │ │ │ +7EF5E PAYLOAD │ │ │ │ + │ │ │ │ +7FE31 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +7FE35 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7FE36 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7FE37 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7FE39 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7FE3B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +7FE3F CRC 2DB7929F (767005343) │ │ │ │ +7FE43 Compressed Size 00000113 (275) │ │ │ │ +7FE47 Uncompressed Size 000001F3 (499) │ │ │ │ +7FE4B Filename Length 001B (27) │ │ │ │ +7FE4D Extra Length 001C (28) │ │ │ │ +7FE4F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7FE4F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7FE6A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7FE6C Length 0009 (9) │ │ │ │ +7FE6E Flags 03 (3) 'Modification Access' │ │ │ │ +7FE6F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7FE73 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7FE77 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7FE79 Length 000B (11) │ │ │ │ +7FE7B Version 01 (1) │ │ │ │ +7FE7C UID Size 04 (4) │ │ │ │ +7FE7D UID 00000000 (0) │ │ │ │ +7FE81 GID Size 04 (4) │ │ │ │ +7FE82 GID 00000000 (0) │ │ │ │ +7FE86 PAYLOAD │ │ │ │ + │ │ │ │ +7FF99 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +7FF9D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7FF9E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7FF9F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7FFA1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7FFA3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +7FFA7 CRC CA00EBE0 (3389058016) │ │ │ │ +7FFAB Compressed Size 0000188D (6285) │ │ │ │ +7FFAF Uncompressed Size 00008FA8 (36776) │ │ │ │ +7FFB3 Filename Length 001D (29) │ │ │ │ +7FFB5 Extra Length 001C (28) │ │ │ │ +7FFB7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7FFB7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7FFD4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7FFD6 Length 0009 (9) │ │ │ │ +7FFD8 Flags 03 (3) 'Modification Access' │ │ │ │ +7FFD9 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7FFDD Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +7FFE1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7FFE3 Length 000B (11) │ │ │ │ +7FFE5 Version 01 (1) │ │ │ │ +7FFE6 UID Size 04 (4) │ │ │ │ +7FFE7 UID 00000000 (0) │ │ │ │ +7FFEB GID Size 04 (4) │ │ │ │ +7FFEC GID 00000000 (0) │ │ │ │ +7FFF0 PAYLOAD │ │ │ │ + │ │ │ │ +8187D LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +81881 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81882 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81883 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +81885 Compression Method 0008 (8) 'Deflated' │ │ │ │ +81887 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +8188B CRC 13F8A234 (335061556) │ │ │ │ +8188F Compressed Size 0000164C (5708) │ │ │ │ +81893 Uncompressed Size 00003A9B (15003) │ │ │ │ +81897 Filename Length 0015 (21) │ │ │ │ +81899 Extra Length 001C (28) │ │ │ │ +8189B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8189B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +818B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +818B2 Length 0009 (9) │ │ │ │ +818B4 Flags 03 (3) 'Modification Access' │ │ │ │ +818B5 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +818B9 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +818BD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +818BF Length 000B (11) │ │ │ │ +818C1 Version 01 (1) │ │ │ │ +818C2 UID Size 04 (4) │ │ │ │ +818C3 UID 00000000 (0) │ │ │ │ +818C7 GID Size 04 (4) │ │ │ │ +818C8 GID 00000000 (0) │ │ │ │ +818CC PAYLOAD │ │ │ │ + │ │ │ │ +82F18 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +82F1C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +82F1D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +82F1E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +82F20 Compression Method 0008 (8) 'Deflated' │ │ │ │ +82F22 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +82F26 CRC A6BC1ACF (2797345487) │ │ │ │ +82F2A Compressed Size 000040C8 (16584) │ │ │ │ +82F2E Uncompressed Size 000133AC (78764) │ │ │ │ +82F32 Filename Length 0016 (22) │ │ │ │ +82F34 Extra Length 001C (28) │ │ │ │ +82F36 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x82F36: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +82F4C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +82F4E Length 0009 (9) │ │ │ │ +82F50 Flags 03 (3) 'Modification Access' │ │ │ │ +82F51 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +82F55 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +82F59 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +82F5B Length 000B (11) │ │ │ │ +82F5D Version 01 (1) │ │ │ │ +82F5E UID Size 04 (4) │ │ │ │ +82F5F UID 00000000 (0) │ │ │ │ +82F63 GID Size 04 (4) │ │ │ │ +82F64 GID 00000000 (0) │ │ │ │ +82F68 PAYLOAD │ │ │ │ │ │ │ │ 87030 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ 87034 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 87035 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 87036 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 87038 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8703A Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8703E CRC FF953A42 (4287969858) │ │ │ │ -87042 Compressed Size 00003EB3 (16051) │ │ │ │ +8703A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +8703E CRC 3D47632B (1028088619) │ │ │ │ +87042 Compressed Size 00003EB7 (16055) │ │ │ │ 87046 Uncompressed Size 0001C78B (116619) │ │ │ │ 8704A Filename Length 0019 (25) │ │ │ │ 8704C Extra Length 001C (28) │ │ │ │ 8704E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x8704E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 87067 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 87069 Length 0009 (9) │ │ │ │ 8706B Flags 03 (3) 'Modification Access' │ │ │ │ -8706C Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -87070 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ +8706C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +87070 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ 87074 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 87076 Length 000B (11) │ │ │ │ 87078 Version 01 (1) │ │ │ │ 87079 UID Size 04 (4) │ │ │ │ 8707A UID 00000000 (0) │ │ │ │ 8707E GID Size 04 (4) │ │ │ │ 8707F GID 00000000 (0) │ │ │ │ 87083 PAYLOAD │ │ │ │ │ │ │ │ -8AF36 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -8AF3A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8AF3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8AF3C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8AF3E Compression Method 0008 (8) 'Deflated' │ │ │ │ -8AF40 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8AF44 CRC D20AC80F (3523921935) │ │ │ │ -8AF48 Compressed Size 0000089D (2205) │ │ │ │ -8AF4C Uncompressed Size 000036CC (14028) │ │ │ │ -8AF50 Filename Length 0011 (17) │ │ │ │ -8AF52 Extra Length 001C (28) │ │ │ │ -8AF54 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8AF54: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8AF65 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8AF67 Length 0009 (9) │ │ │ │ -8AF69 Flags 03 (3) 'Modification Access' │ │ │ │ -8AF6A Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8AF6E Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8AF72 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8AF74 Length 000B (11) │ │ │ │ -8AF76 Version 01 (1) │ │ │ │ -8AF77 UID Size 04 (4) │ │ │ │ -8AF78 UID 00000000 (0) │ │ │ │ -8AF7C GID Size 04 (4) │ │ │ │ -8AF7D GID 00000000 (0) │ │ │ │ -8AF81 PAYLOAD │ │ │ │ - │ │ │ │ -8B81E LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -8B822 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8B823 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8B824 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8B826 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8B828 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8B82C CRC ACBCD47B (2898056315) │ │ │ │ -8B830 Compressed Size 0000519E (20894) │ │ │ │ -8B834 Uncompressed Size 0001F99A (129434) │ │ │ │ -8B838 Filename Length 0015 (21) │ │ │ │ -8B83A Extra Length 001C (28) │ │ │ │ -8B83C Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8B83C: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8B851 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8B853 Length 0009 (9) │ │ │ │ -8B855 Flags 03 (3) 'Modification Access' │ │ │ │ -8B856 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8B85A Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -8B85E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8B860 Length 000B (11) │ │ │ │ -8B862 Version 01 (1) │ │ │ │ -8B863 UID Size 04 (4) │ │ │ │ -8B864 UID 00000000 (0) │ │ │ │ -8B868 GID Size 04 (4) │ │ │ │ -8B869 GID 00000000 (0) │ │ │ │ -8B86D PAYLOAD │ │ │ │ - │ │ │ │ -90A0B LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -90A0F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -90A10 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -90A11 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -90A13 Compression Method 0008 (8) 'Deflated' │ │ │ │ -90A15 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -90A19 CRC 2CBD0730 (750585648) │ │ │ │ -90A1D Compressed Size 00001C41 (7233) │ │ │ │ -90A21 Uncompressed Size 00008AC8 (35528) │ │ │ │ -90A25 Filename Length 0019 (25) │ │ │ │ -90A27 Extra Length 001C (28) │ │ │ │ -90A29 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x90A29: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -90A42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -90A44 Length 0009 (9) │ │ │ │ -90A46 Flags 03 (3) 'Modification Access' │ │ │ │ -90A47 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -90A4B Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -90A4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -90A51 Length 000B (11) │ │ │ │ -90A53 Version 01 (1) │ │ │ │ -90A54 UID Size 04 (4) │ │ │ │ -90A55 UID 00000000 (0) │ │ │ │ -90A59 GID Size 04 (4) │ │ │ │ -90A5A GID 00000000 (0) │ │ │ │ -90A5E PAYLOAD │ │ │ │ - │ │ │ │ -9269F LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -926A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -926A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -926A5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -926A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -926A9 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -926AD CRC 896CE926 (2305616166) │ │ │ │ -926B1 Compressed Size 00000D91 (3473) │ │ │ │ -926B5 Uncompressed Size 00002EA4 (11940) │ │ │ │ -926B9 Filename Length 0018 (24) │ │ │ │ -926BB Extra Length 001C (28) │ │ │ │ -926BD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x926BD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -926D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -926D7 Length 0009 (9) │ │ │ │ -926D9 Flags 03 (3) 'Modification Access' │ │ │ │ -926DA Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -926DE Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -926E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -926E4 Length 000B (11) │ │ │ │ -926E6 Version 01 (1) │ │ │ │ -926E7 UID Size 04 (4) │ │ │ │ -926E8 UID 00000000 (0) │ │ │ │ -926EC GID Size 04 (4) │ │ │ │ -926ED GID 00000000 (0) │ │ │ │ -926F1 PAYLOAD │ │ │ │ - │ │ │ │ -93482 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -93486 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -93487 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -93488 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9348A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9348C Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -93490 CRC 535B63FB (1398498299) │ │ │ │ -93494 Compressed Size 000001DF (479) │ │ │ │ -93498 Uncompressed Size 00000323 (803) │ │ │ │ -9349C Filename Length 0011 (17) │ │ │ │ -9349E Extra Length 001C (28) │ │ │ │ -934A0 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x934A0: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -934B1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -934B3 Length 0009 (9) │ │ │ │ -934B5 Flags 03 (3) 'Modification Access' │ │ │ │ -934B6 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -934BA Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -934BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -934C0 Length 000B (11) │ │ │ │ -934C2 Version 01 (1) │ │ │ │ -934C3 UID Size 04 (4) │ │ │ │ -934C4 UID 00000000 (0) │ │ │ │ -934C8 GID Size 04 (4) │ │ │ │ -934C9 GID 00000000 (0) │ │ │ │ -934CD PAYLOAD │ │ │ │ - │ │ │ │ -936AC LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -936B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -936B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -936B2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -936B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -936B6 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -936BA CRC 32CAD6D8 (852154072) │ │ │ │ -936BE Compressed Size 000006BE (1726) │ │ │ │ -936C2 Uncompressed Size 0000141F (5151) │ │ │ │ -936C6 Filename Length 0019 (25) │ │ │ │ -936C8 Extra Length 001C (28) │ │ │ │ -936CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x936CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -936E3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -936E5 Length 0009 (9) │ │ │ │ -936E7 Flags 03 (3) 'Modification Access' │ │ │ │ -936E8 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -936EC Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -936F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -936F2 Length 000B (11) │ │ │ │ -936F4 Version 01 (1) │ │ │ │ -936F5 UID Size 04 (4) │ │ │ │ -936F6 UID 00000000 (0) │ │ │ │ -936FA GID Size 04 (4) │ │ │ │ -936FB GID 00000000 (0) │ │ │ │ -936FF PAYLOAD │ │ │ │ - │ │ │ │ -93DBD LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -93DC1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -93DC2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -93DC3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -93DC5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -93DC7 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -93DCB CRC FC6D6CD0 (4235029712) │ │ │ │ -93DCF Compressed Size 00001B8D (7053) │ │ │ │ -93DD3 Uncompressed Size 00009F5F (40799) │ │ │ │ -93DD7 Filename Length 0018 (24) │ │ │ │ -93DD9 Extra Length 001C (28) │ │ │ │ -93DDB Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x93DDB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -93DF3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -93DF5 Length 0009 (9) │ │ │ │ -93DF7 Flags 03 (3) 'Modification Access' │ │ │ │ -93DF8 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -93DFC Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -93E00 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -93E02 Length 000B (11) │ │ │ │ -93E04 Version 01 (1) │ │ │ │ -93E05 UID Size 04 (4) │ │ │ │ -93E06 UID 00000000 (0) │ │ │ │ -93E0A GID Size 04 (4) │ │ │ │ -93E0B GID 00000000 (0) │ │ │ │ -93E0F PAYLOAD │ │ │ │ - │ │ │ │ -9599C LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -959A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -959A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -959A2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -959A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -959A6 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -959AA CRC 11186130 (286810416) │ │ │ │ -959AE Compressed Size 000016FF (5887) │ │ │ │ -959B2 Uncompressed Size 00008B12 (35602) │ │ │ │ -959B6 Filename Length 0012 (18) │ │ │ │ -959B8 Extra Length 001C (28) │ │ │ │ -959BA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x959BA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -959CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -959CE Length 0009 (9) │ │ │ │ -959D0 Flags 03 (3) 'Modification Access' │ │ │ │ -959D1 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -959D5 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -959D9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -959DB Length 000B (11) │ │ │ │ -959DD Version 01 (1) │ │ │ │ -959DE UID Size 04 (4) │ │ │ │ -959DF UID 00000000 (0) │ │ │ │ -959E3 GID Size 04 (4) │ │ │ │ -959E4 GID 00000000 (0) │ │ │ │ -959E8 PAYLOAD │ │ │ │ - │ │ │ │ -970E7 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -970EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -970EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -970ED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -970EF Compression Method 0008 (8) 'Deflated' │ │ │ │ -970F1 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -970F5 CRC 0D2BB7BA (220968890) │ │ │ │ -970F9 Compressed Size 00001E0B (7691) │ │ │ │ -970FD Uncompressed Size 00008823 (34851) │ │ │ │ -97101 Filename Length 0016 (22) │ │ │ │ -97103 Extra Length 001C (28) │ │ │ │ -97105 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x97105: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9711B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9711D Length 0009 (9) │ │ │ │ -9711F Flags 03 (3) 'Modification Access' │ │ │ │ -97120 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -97124 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -97128 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9712A Length 000B (11) │ │ │ │ -9712C Version 01 (1) │ │ │ │ -9712D UID Size 04 (4) │ │ │ │ -9712E UID 00000000 (0) │ │ │ │ -97132 GID Size 04 (4) │ │ │ │ -97133 GID 00000000 (0) │ │ │ │ -97137 PAYLOAD │ │ │ │ - │ │ │ │ -98F42 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -98F46 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -98F47 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -98F48 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -98F4A Compression Method 0008 (8) 'Deflated' │ │ │ │ -98F4C Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -98F50 CRC AA93B274 (2861806196) │ │ │ │ -98F54 Compressed Size 000029A8 (10664) │ │ │ │ -98F58 Uncompressed Size 0000D04F (53327) │ │ │ │ -98F5C Filename Length 001A (26) │ │ │ │ -98F5E Extra Length 001C (28) │ │ │ │ -98F60 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x98F60: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -98F7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -98F7C Length 0009 (9) │ │ │ │ -98F7E Flags 03 (3) 'Modification Access' │ │ │ │ -98F7F Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -98F83 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -98F87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -98F89 Length 000B (11) │ │ │ │ -98F8B Version 01 (1) │ │ │ │ -98F8C UID Size 04 (4) │ │ │ │ -98F8D UID 00000000 (0) │ │ │ │ -98F91 GID Size 04 (4) │ │ │ │ -98F92 GID 00000000 (0) │ │ │ │ -98F96 PAYLOAD │ │ │ │ - │ │ │ │ -9B93E LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -9B942 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9B943 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9B944 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9B946 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9B948 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9B94C CRC C898661A (3365430810) │ │ │ │ -9B950 Compressed Size 000009AB (2475) │ │ │ │ -9B954 Uncompressed Size 00001DAC (7596) │ │ │ │ -9B958 Filename Length 0018 (24) │ │ │ │ -9B95A Extra Length 001C (28) │ │ │ │ -9B95C Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9B95C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9B974 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9B976 Length 0009 (9) │ │ │ │ -9B978 Flags 03 (3) 'Modification Access' │ │ │ │ -9B979 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9B97D Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9B981 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9B983 Length 000B (11) │ │ │ │ -9B985 Version 01 (1) │ │ │ │ -9B986 UID Size 04 (4) │ │ │ │ -9B987 UID 00000000 (0) │ │ │ │ -9B98B GID Size 04 (4) │ │ │ │ -9B98C GID 00000000 (0) │ │ │ │ -9B990 PAYLOAD │ │ │ │ - │ │ │ │ -9C33B LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -9C33F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9C340 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9C341 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9C343 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9C345 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9C349 CRC F0556E9A (4032130714) │ │ │ │ -9C34D Compressed Size 000152EE (86766) │ │ │ │ -9C351 Uncompressed Size 000159F8 (88568) │ │ │ │ -9C355 Filename Length 001E (30) │ │ │ │ -9C357 Extra Length 001C (28) │ │ │ │ -9C359 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9C359: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9C377 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9C379 Length 0009 (9) │ │ │ │ -9C37B Flags 03 (3) 'Modification Access' │ │ │ │ -9C37C Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9C380 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -9C384 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9C386 Length 000B (11) │ │ │ │ -9C388 Version 01 (1) │ │ │ │ -9C389 UID Size 04 (4) │ │ │ │ -9C38A UID 00000000 (0) │ │ │ │ -9C38E GID Size 04 (4) │ │ │ │ -9C38F GID 00000000 (0) │ │ │ │ -9C393 PAYLOAD │ │ │ │ - │ │ │ │ -B1681 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -B1685 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B1686 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B1687 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B1689 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B168B Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B168F CRC F5E2129F (4125233823) │ │ │ │ -B1693 Compressed Size 000016BC (5820) │ │ │ │ -B1697 Uncompressed Size 000016CD (5837) │ │ │ │ -B169B Filename Length 0015 (21) │ │ │ │ -B169D Extra Length 001C (28) │ │ │ │ -B169F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB169F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B16B4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B16B6 Length 0009 (9) │ │ │ │ -B16B8 Flags 03 (3) 'Modification Access' │ │ │ │ -B16B9 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B16BD Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B16C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B16C3 Length 000B (11) │ │ │ │ -B16C5 Version 01 (1) │ │ │ │ -B16C6 UID Size 04 (4) │ │ │ │ -B16C7 UID 00000000 (0) │ │ │ │ -B16CB GID Size 04 (4) │ │ │ │ -B16CC GID 00000000 (0) │ │ │ │ -B16D0 PAYLOAD │ │ │ │ - │ │ │ │ -B2D8C LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -B2D90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B2D91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B2D92 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B2D94 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B2D96 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B2D9A CRC F5E2129F (4125233823) │ │ │ │ -B2D9E Compressed Size 000016BC (5820) │ │ │ │ -B2DA2 Uncompressed Size 000016CD (5837) │ │ │ │ -B2DA6 Filename Length 001C (28) │ │ │ │ -B2DA8 Extra Length 001C (28) │ │ │ │ -B2DAA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB2DAA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B2DC6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B2DC8 Length 0009 (9) │ │ │ │ -B2DCA Flags 03 (3) 'Modification Access' │ │ │ │ -B2DCB Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B2DCF Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B2DD3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B2DD5 Length 000B (11) │ │ │ │ -B2DD7 Version 01 (1) │ │ │ │ -B2DD8 UID Size 04 (4) │ │ │ │ -B2DD9 UID 00000000 (0) │ │ │ │ -B2DDD GID Size 04 (4) │ │ │ │ -B2DDE GID 00000000 (0) │ │ │ │ -B2DE2 PAYLOAD │ │ │ │ - │ │ │ │ -B449E LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -B44A2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B44A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B44A4 General Purpose Flag 0000 (0) │ │ │ │ -B44A6 Compression Method 0000 (0) 'Stored' │ │ │ │ -B44A8 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B44AC CRC FC95F24B (4237685323) │ │ │ │ -B44B0 Compressed Size 00001B84 (7044) │ │ │ │ -B44B4 Uncompressed Size 00001B84 (7044) │ │ │ │ -B44B8 Filename Length 0016 (22) │ │ │ │ -B44BA Extra Length 001C (28) │ │ │ │ -B44BC Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB44BC: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B44D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B44D4 Length 0009 (9) │ │ │ │ -B44D6 Flags 03 (3) 'Modification Access' │ │ │ │ -B44D7 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B44DB Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B44DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B44E1 Length 000B (11) │ │ │ │ -B44E3 Version 01 (1) │ │ │ │ -B44E4 UID Size 04 (4) │ │ │ │ -B44E5 UID 00000000 (0) │ │ │ │ -B44E9 GID Size 04 (4) │ │ │ │ -B44EA GID 00000000 (0) │ │ │ │ -B44EE PAYLOAD │ │ │ │ - │ │ │ │ -B6072 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -B6076 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B6077 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B6078 General Purpose Flag 0000 (0) │ │ │ │ -B607A Compression Method 0000 (0) 'Stored' │ │ │ │ -B607C Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B6080 CRC D0D71F86 (3503759238) │ │ │ │ -B6084 Compressed Size 00000B7B (2939) │ │ │ │ -B6088 Uncompressed Size 00000B7B (2939) │ │ │ │ -B608C Filename Length 0016 (22) │ │ │ │ -B608E Extra Length 001C (28) │ │ │ │ -B6090 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB6090: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B60A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B60A8 Length 0009 (9) │ │ │ │ -B60AA Flags 03 (3) 'Modification Access' │ │ │ │ -B60AB Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B60AF Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B60B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B60B5 Length 000B (11) │ │ │ │ -B60B7 Version 01 (1) │ │ │ │ -B60B8 UID Size 04 (4) │ │ │ │ -B60B9 UID 00000000 (0) │ │ │ │ -B60BD GID Size 04 (4) │ │ │ │ -B60BE GID 00000000 (0) │ │ │ │ -B60C2 PAYLOAD │ │ │ │ - │ │ │ │ -B6C3D LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -B6C41 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B6C42 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B6C43 General Purpose Flag 0000 (0) │ │ │ │ -B6C45 Compression Method 0000 (0) 'Stored' │ │ │ │ -B6C47 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B6C4B CRC FFF9C4D2 (4294558930) │ │ │ │ -B6C4F Compressed Size 0000138F (5007) │ │ │ │ -B6C53 Uncompressed Size 0000138F (5007) │ │ │ │ -B6C57 Filename Length 0016 (22) │ │ │ │ -B6C59 Extra Length 001C (28) │ │ │ │ -B6C5B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB6C5B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B6C71 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B6C73 Length 0009 (9) │ │ │ │ -B6C75 Flags 03 (3) 'Modification Access' │ │ │ │ -B6C76 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B6C7A Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B6C7E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B6C80 Length 000B (11) │ │ │ │ -B6C82 Version 01 (1) │ │ │ │ -B6C83 UID Size 04 (4) │ │ │ │ -B6C84 UID 00000000 (0) │ │ │ │ -B6C88 GID Size 04 (4) │ │ │ │ -B6C89 GID 00000000 (0) │ │ │ │ -B6C8D PAYLOAD │ │ │ │ - │ │ │ │ -B801C LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -B8020 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B8021 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8022 General Purpose Flag 0000 (0) │ │ │ │ -B8024 Compression Method 0000 (0) 'Stored' │ │ │ │ -B8026 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B802A CRC A1037E8E (2701360782) │ │ │ │ -B802E Compressed Size 0000145E (5214) │ │ │ │ -B8032 Uncompressed Size 0000145E (5214) │ │ │ │ -B8036 Filename Length 0016 (22) │ │ │ │ -B8038 Extra Length 001C (28) │ │ │ │ -B803A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB803A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8050 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8052 Length 0009 (9) │ │ │ │ -B8054 Flags 03 (3) 'Modification Access' │ │ │ │ -B8055 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B8059 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B805D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B805F Length 000B (11) │ │ │ │ -B8061 Version 01 (1) │ │ │ │ -B8062 UID Size 04 (4) │ │ │ │ -B8063 UID 00000000 (0) │ │ │ │ -B8067 GID Size 04 (4) │ │ │ │ -B8068 GID 00000000 (0) │ │ │ │ -B806C PAYLOAD │ │ │ │ - │ │ │ │ -B94CA LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ -B94CE Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B94CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B94D0 General Purpose Flag 0000 (0) │ │ │ │ -B94D2 Compression Method 0000 (0) 'Stored' │ │ │ │ -B94D4 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B94D8 CRC 5E9E64F1 (1587438833) │ │ │ │ -B94DC Compressed Size 000008EC (2284) │ │ │ │ -B94E0 Uncompressed Size 000008EC (2284) │ │ │ │ -B94E4 Filename Length 0016 (22) │ │ │ │ -B94E6 Extra Length 001C (28) │ │ │ │ -B94E8 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB94E8: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B94FE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9500 Length 0009 (9) │ │ │ │ -B9502 Flags 03 (3) 'Modification Access' │ │ │ │ -B9503 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B9507 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 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 PAYLOAD │ │ │ │ - │ │ │ │ -B9E06 LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ -B9E0A Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B9E0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9E0C General Purpose Flag 0000 (0) │ │ │ │ -B9E0E Compression Method 0000 (0) 'Stored' │ │ │ │ -B9E10 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B9E14 CRC 42E340AB (1122189483) │ │ │ │ -B9E18 Compressed Size 00001F2E (7982) │ │ │ │ -B9E1C Uncompressed Size 00001F2E (7982) │ │ │ │ -B9E20 Filename Length 001E (30) │ │ │ │ -B9E22 Extra Length 001C (28) │ │ │ │ -B9E24 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9E24: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9E42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9E44 Length 0009 (9) │ │ │ │ -B9E46 Flags 03 (3) 'Modification Access' │ │ │ │ -B9E47 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B9E4B Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -B9E4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9E51 Length 000B (11) │ │ │ │ -B9E53 Version 01 (1) │ │ │ │ -B9E54 UID Size 04 (4) │ │ │ │ -B9E55 UID 00000000 (0) │ │ │ │ -B9E59 GID Size 04 (4) │ │ │ │ -B9E5A GID 00000000 (0) │ │ │ │ -B9E5E PAYLOAD │ │ │ │ - │ │ │ │ -BBD8C LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ -BBD90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BBD91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BBD92 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BBD94 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BBD96 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BBD9A CRC 29EA8E7B (703237755) │ │ │ │ -BBD9E Compressed Size 00003D82 (15746) │ │ │ │ -BBDA2 Uncompressed Size 000166B0 (91824) │ │ │ │ -BBDA6 Filename Length 001A (26) │ │ │ │ -BBDA8 Extra Length 001C (28) │ │ │ │ -BBDAA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBBDAA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BBDC4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BBDC6 Length 0009 (9) │ │ │ │ -BBDC8 Flags 03 (3) 'Modification Access' │ │ │ │ -BBDC9 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BBDCD Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BBDD1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BBDD3 Length 000B (11) │ │ │ │ -BBDD5 Version 01 (1) │ │ │ │ -BBDD6 UID Size 04 (4) │ │ │ │ -BBDD7 UID 00000000 (0) │ │ │ │ -BBDDB GID Size 04 (4) │ │ │ │ -BBDDC GID 00000000 (0) │ │ │ │ -BBDE0 PAYLOAD │ │ │ │ - │ │ │ │ -BFB62 LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ -BFB66 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BFB67 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BFB68 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BFB6A Compression Method 0008 (8) 'Deflated' │ │ │ │ -BFB6C Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BFB70 CRC 207E153D (545133885) │ │ │ │ -BFB74 Compressed Size 000029D2 (10706) │ │ │ │ -BFB78 Uncompressed Size 0000BB3A (47930) │ │ │ │ -BFB7C Filename Length 0018 (24) │ │ │ │ -BFB7E Extra Length 001C (28) │ │ │ │ -BFB80 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBFB80: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BFB98 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BFB9A Length 0009 (9) │ │ │ │ -BFB9C Flags 03 (3) 'Modification Access' │ │ │ │ -BFB9D Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BFBA1 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -BFBA5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BFBA7 Length 000B (11) │ │ │ │ -BFBA9 Version 01 (1) │ │ │ │ -BFBAA UID Size 04 (4) │ │ │ │ -BFBAB UID 00000000 (0) │ │ │ │ -BFBAF GID Size 04 (4) │ │ │ │ -BFBB0 GID 00000000 (0) │ │ │ │ -BFBB4 PAYLOAD │ │ │ │ - │ │ │ │ -C2586 LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ -C258A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C258B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C258C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C258E Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2590 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C2594 CRC DCB3B516 (3702764822) │ │ │ │ -C2598 Compressed Size 000000AE (174) │ │ │ │ -C259C Uncompressed Size 000000FC (252) │ │ │ │ -C25A0 Filename Length 0016 (22) │ │ │ │ -C25A2 Extra Length 001C (28) │ │ │ │ -C25A4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC25A4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C25BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C25BC Length 0009 (9) │ │ │ │ -C25BE Flags 03 (3) 'Modification Access' │ │ │ │ -C25BF Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C25C3 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C25C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C25C9 Length 000B (11) │ │ │ │ -C25CB Version 01 (1) │ │ │ │ -C25CC UID Size 04 (4) │ │ │ │ -C25CD UID 00000000 (0) │ │ │ │ -C25D1 GID Size 04 (4) │ │ │ │ -C25D2 GID 00000000 (0) │ │ │ │ -C25D6 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +8AF3A LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +8AF3E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8AF3F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8AF40 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8AF42 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8AF44 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +8AF48 CRC 339E52AB (866013867) │ │ │ │ +8AF4C Compressed Size 000008A3 (2211) │ │ │ │ +8AF50 Uncompressed Size 000036CC (14028) │ │ │ │ +8AF54 Filename Length 0011 (17) │ │ │ │ +8AF56 Extra Length 001C (28) │ │ │ │ +8AF58 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8AF58: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8AF69 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8AF6B Length 0009 (9) │ │ │ │ +8AF6D Flags 03 (3) 'Modification Access' │ │ │ │ +8AF6E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +8AF72 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +8AF76 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8AF78 Length 000B (11) │ │ │ │ +8AF7A Version 01 (1) │ │ │ │ +8AF7B UID Size 04 (4) │ │ │ │ +8AF7C UID 00000000 (0) │ │ │ │ +8AF80 GID Size 04 (4) │ │ │ │ +8AF81 GID 00000000 (0) │ │ │ │ +8AF85 PAYLOAD │ │ │ │ + │ │ │ │ +8B828 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +8B82C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8B82D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8B82E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8B830 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8B832 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +8B836 CRC FFBD87B5 (4290611125) │ │ │ │ +8B83A Compressed Size 000051A3 (20899) │ │ │ │ +8B83E Uncompressed Size 0001F99A (129434) │ │ │ │ +8B842 Filename Length 0015 (21) │ │ │ │ +8B844 Extra Length 001C (28) │ │ │ │ +8B846 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8B846: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8B85B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8B85D Length 0009 (9) │ │ │ │ +8B85F Flags 03 (3) 'Modification Access' │ │ │ │ +8B860 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +8B864 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +8B868 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8B86A Length 000B (11) │ │ │ │ +8B86C Version 01 (1) │ │ │ │ +8B86D UID Size 04 (4) │ │ │ │ +8B86E UID 00000000 (0) │ │ │ │ +8B872 GID Size 04 (4) │ │ │ │ +8B873 GID 00000000 (0) │ │ │ │ +8B877 PAYLOAD │ │ │ │ + │ │ │ │ +90A1A LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +90A1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +90A1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +90A20 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +90A22 Compression Method 0008 (8) 'Deflated' │ │ │ │ +90A24 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +90A28 CRC 1BAA9D20 (464166176) │ │ │ │ +90A2C Compressed Size 00001C43 (7235) │ │ │ │ +90A30 Uncompressed Size 00008AC8 (35528) │ │ │ │ +90A34 Filename Length 0019 (25) │ │ │ │ +90A36 Extra Length 001C (28) │ │ │ │ +90A38 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x90A38: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +90A51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +90A53 Length 0009 (9) │ │ │ │ +90A55 Flags 03 (3) 'Modification Access' │ │ │ │ +90A56 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +90A5A Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +90A5E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +90A60 Length 000B (11) │ │ │ │ +90A62 Version 01 (1) │ │ │ │ +90A63 UID Size 04 (4) │ │ │ │ +90A64 UID 00000000 (0) │ │ │ │ +90A68 GID Size 04 (4) │ │ │ │ +90A69 GID 00000000 (0) │ │ │ │ +90A6D PAYLOAD │ │ │ │ + │ │ │ │ +926B0 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +926B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +926B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +926B6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +926B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +926BA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +926BE CRC FAE8837D (4209542013) │ │ │ │ +926C2 Compressed Size 00000D92 (3474) │ │ │ │ +926C6 Uncompressed Size 00002EA4 (11940) │ │ │ │ +926CA Filename Length 0018 (24) │ │ │ │ +926CC Extra Length 001C (28) │ │ │ │ +926CE Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x926CE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +926E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +926E8 Length 0009 (9) │ │ │ │ +926EA Flags 03 (3) 'Modification Access' │ │ │ │ +926EB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +926EF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +926F3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +926F5 Length 000B (11) │ │ │ │ +926F7 Version 01 (1) │ │ │ │ +926F8 UID Size 04 (4) │ │ │ │ +926F9 UID 00000000 (0) │ │ │ │ +926FD GID Size 04 (4) │ │ │ │ +926FE GID 00000000 (0) │ │ │ │ +92702 PAYLOAD │ │ │ │ + │ │ │ │ +93494 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +93498 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +93499 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9349A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9349C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9349E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +934A2 CRC 535B63FB (1398498299) │ │ │ │ +934A6 Compressed Size 000001DF (479) │ │ │ │ +934AA Uncompressed Size 00000323 (803) │ │ │ │ +934AE Filename Length 0011 (17) │ │ │ │ +934B0 Extra Length 001C (28) │ │ │ │ +934B2 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x934B2: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +934C3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +934C5 Length 0009 (9) │ │ │ │ +934C7 Flags 03 (3) 'Modification Access' │ │ │ │ +934C8 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +934CC Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +934D0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +934D2 Length 000B (11) │ │ │ │ +934D4 Version 01 (1) │ │ │ │ +934D5 UID Size 04 (4) │ │ │ │ +934D6 UID 00000000 (0) │ │ │ │ +934DA GID Size 04 (4) │ │ │ │ +934DB GID 00000000 (0) │ │ │ │ +934DF PAYLOAD │ │ │ │ + │ │ │ │ +936BE LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +936C2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +936C3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +936C4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +936C6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +936C8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +936CC CRC 32CAD6D8 (852154072) │ │ │ │ +936D0 Compressed Size 000006BE (1726) │ │ │ │ +936D4 Uncompressed Size 0000141F (5151) │ │ │ │ +936D8 Filename Length 0019 (25) │ │ │ │ +936DA Extra Length 001C (28) │ │ │ │ +936DC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x936DC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +936F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +936F7 Length 0009 (9) │ │ │ │ +936F9 Flags 03 (3) 'Modification Access' │ │ │ │ +936FA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +936FE Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +93702 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +93704 Length 000B (11) │ │ │ │ +93706 Version 01 (1) │ │ │ │ +93707 UID Size 04 (4) │ │ │ │ +93708 UID 00000000 (0) │ │ │ │ +9370C GID Size 04 (4) │ │ │ │ +9370D GID 00000000 (0) │ │ │ │ +93711 PAYLOAD │ │ │ │ + │ │ │ │ +93DCF LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +93DD3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +93DD4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +93DD5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +93DD7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +93DD9 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +93DDD CRC 3082FD4D (813890893) │ │ │ │ +93DE1 Compressed Size 00001B8B (7051) │ │ │ │ +93DE5 Uncompressed Size 00009F5F (40799) │ │ │ │ +93DE9 Filename Length 0018 (24) │ │ │ │ +93DEB Extra Length 001C (28) │ │ │ │ +93DED Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x93DED: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +93E05 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +93E07 Length 0009 (9) │ │ │ │ +93E09 Flags 03 (3) 'Modification Access' │ │ │ │ +93E0A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +93E0E Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +93E12 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +93E14 Length 000B (11) │ │ │ │ +93E16 Version 01 (1) │ │ │ │ +93E17 UID Size 04 (4) │ │ │ │ +93E18 UID 00000000 (0) │ │ │ │ +93E1C GID Size 04 (4) │ │ │ │ +93E1D GID 00000000 (0) │ │ │ │ +93E21 PAYLOAD │ │ │ │ + │ │ │ │ +959AC LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +959B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +959B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +959B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +959B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +959B6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +959BA CRC 1E04A61F (503621151) │ │ │ │ +959BE Compressed Size 00001700 (5888) │ │ │ │ +959C2 Uncompressed Size 00008B12 (35602) │ │ │ │ +959C6 Filename Length 0012 (18) │ │ │ │ +959C8 Extra Length 001C (28) │ │ │ │ +959CA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x959CA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +959DC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +959DE Length 0009 (9) │ │ │ │ +959E0 Flags 03 (3) 'Modification Access' │ │ │ │ +959E1 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +959E5 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +959E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +959EB Length 000B (11) │ │ │ │ +959ED Version 01 (1) │ │ │ │ +959EE UID Size 04 (4) │ │ │ │ +959EF UID 00000000 (0) │ │ │ │ +959F3 GID Size 04 (4) │ │ │ │ +959F4 GID 00000000 (0) │ │ │ │ +959F8 PAYLOAD │ │ │ │ + │ │ │ │ +970F8 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +970FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +970FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +970FE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +97100 Compression Method 0008 (8) 'Deflated' │ │ │ │ +97102 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +97106 CRC C9194D7F (3373878655) │ │ │ │ +9710A Compressed Size 00001E0B (7691) │ │ │ │ +9710E Uncompressed Size 00008823 (34851) │ │ │ │ +97112 Filename Length 0016 (22) │ │ │ │ +97114 Extra Length 001C (28) │ │ │ │ +97116 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x97116: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9712C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9712E Length 0009 (9) │ │ │ │ +97130 Flags 03 (3) 'Modification Access' │ │ │ │ +97131 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +97135 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +97139 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9713B Length 000B (11) │ │ │ │ +9713D Version 01 (1) │ │ │ │ +9713E UID Size 04 (4) │ │ │ │ +9713F UID 00000000 (0) │ │ │ │ +97143 GID Size 04 (4) │ │ │ │ +97144 GID 00000000 (0) │ │ │ │ +97148 PAYLOAD │ │ │ │ + │ │ │ │ +98F53 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +98F57 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +98F58 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +98F59 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +98F5B Compression Method 0008 (8) 'Deflated' │ │ │ │ +98F5D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +98F61 CRC FC5E70B2 (4234047666) │ │ │ │ +98F65 Compressed Size 000029A9 (10665) │ │ │ │ +98F69 Uncompressed Size 0000D04F (53327) │ │ │ │ +98F6D Filename Length 001A (26) │ │ │ │ +98F6F Extra Length 001C (28) │ │ │ │ +98F71 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x98F71: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +98F8B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +98F8D Length 0009 (9) │ │ │ │ +98F8F Flags 03 (3) 'Modification Access' │ │ │ │ +98F90 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +98F94 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +98F98 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +98F9A Length 000B (11) │ │ │ │ +98F9C Version 01 (1) │ │ │ │ +98F9D UID Size 04 (4) │ │ │ │ +98F9E UID 00000000 (0) │ │ │ │ +98FA2 GID Size 04 (4) │ │ │ │ +98FA3 GID 00000000 (0) │ │ │ │ +98FA7 PAYLOAD │ │ │ │ + │ │ │ │ +9B950 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +9B954 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9B955 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9B956 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9B958 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9B95A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +9B95E CRC 8529CAAD (2234108589) │ │ │ │ +9B962 Compressed Size 000009AB (2475) │ │ │ │ +9B966 Uncompressed Size 00001DAC (7596) │ │ │ │ +9B96A Filename Length 0018 (24) │ │ │ │ +9B96C Extra Length 001C (28) │ │ │ │ +9B96E Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9B96E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9B986 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9B988 Length 0009 (9) │ │ │ │ +9B98A Flags 03 (3) 'Modification Access' │ │ │ │ +9B98B Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +9B98F Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +9B993 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9B995 Length 000B (11) │ │ │ │ +9B997 Version 01 (1) │ │ │ │ +9B998 UID Size 04 (4) │ │ │ │ +9B999 UID 00000000 (0) │ │ │ │ +9B99D GID Size 04 (4) │ │ │ │ +9B99E GID 00000000 (0) │ │ │ │ +9B9A2 PAYLOAD │ │ │ │ + │ │ │ │ +9C34D LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +9C351 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9C352 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9C353 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9C355 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9C357 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +9C35B CRC F0556E9A (4032130714) │ │ │ │ +9C35F Compressed Size 000152EE (86766) │ │ │ │ +9C363 Uncompressed Size 000159F8 (88568) │ │ │ │ +9C367 Filename Length 001E (30) │ │ │ │ +9C369 Extra Length 001C (28) │ │ │ │ +9C36B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9C36B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9C389 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9C38B Length 0009 (9) │ │ │ │ +9C38D Flags 03 (3) 'Modification Access' │ │ │ │ +9C38E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +9C392 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +9C396 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9C398 Length 000B (11) │ │ │ │ +9C39A Version 01 (1) │ │ │ │ +9C39B UID Size 04 (4) │ │ │ │ +9C39C UID 00000000 (0) │ │ │ │ +9C3A0 GID Size 04 (4) │ │ │ │ +9C3A1 GID 00000000 (0) │ │ │ │ +9C3A5 PAYLOAD │ │ │ │ + │ │ │ │ +B1693 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +B1697 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B1698 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B1699 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B169B Compression Method 0008 (8) 'Deflated' │ │ │ │ +B169D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B16A1 CRC F5E2129F (4125233823) │ │ │ │ +B16A5 Compressed Size 000016BC (5820) │ │ │ │ +B16A9 Uncompressed Size 000016CD (5837) │ │ │ │ +B16AD Filename Length 0015 (21) │ │ │ │ +B16AF Extra Length 001C (28) │ │ │ │ +B16B1 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB16B1: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B16C6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B16C8 Length 0009 (9) │ │ │ │ +B16CA Flags 03 (3) 'Modification Access' │ │ │ │ +B16CB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B16CF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B16D3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B16D5 Length 000B (11) │ │ │ │ +B16D7 Version 01 (1) │ │ │ │ +B16D8 UID Size 04 (4) │ │ │ │ +B16D9 UID 00000000 (0) │ │ │ │ +B16DD GID Size 04 (4) │ │ │ │ +B16DE GID 00000000 (0) │ │ │ │ +B16E2 PAYLOAD │ │ │ │ + │ │ │ │ +B2D9E LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +B2DA2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B2DA3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B2DA4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B2DA6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B2DA8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B2DAC CRC F5E2129F (4125233823) │ │ │ │ +B2DB0 Compressed Size 000016BC (5820) │ │ │ │ +B2DB4 Uncompressed Size 000016CD (5837) │ │ │ │ +B2DB8 Filename Length 001C (28) │ │ │ │ +B2DBA Extra Length 001C (28) │ │ │ │ +B2DBC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB2DBC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B2DD8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B2DDA Length 0009 (9) │ │ │ │ +B2DDC Flags 03 (3) 'Modification Access' │ │ │ │ +B2DDD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B2DE1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B2DE5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B2DE7 Length 000B (11) │ │ │ │ +B2DE9 Version 01 (1) │ │ │ │ +B2DEA UID Size 04 (4) │ │ │ │ +B2DEB UID 00000000 (0) │ │ │ │ +B2DEF GID Size 04 (4) │ │ │ │ +B2DF0 GID 00000000 (0) │ │ │ │ +B2DF4 PAYLOAD │ │ │ │ + │ │ │ │ +B44B0 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +B44B4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B44B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B44B6 General Purpose Flag 0000 (0) │ │ │ │ +B44B8 Compression Method 0000 (0) 'Stored' │ │ │ │ +B44BA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B44BE CRC FC95F24B (4237685323) │ │ │ │ +B44C2 Compressed Size 00001B84 (7044) │ │ │ │ +B44C6 Uncompressed Size 00001B84 (7044) │ │ │ │ +B44CA Filename Length 0016 (22) │ │ │ │ +B44CC Extra Length 001C (28) │ │ │ │ +B44CE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB44CE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B44E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B44E6 Length 0009 (9) │ │ │ │ +B44E8 Flags 03 (3) 'Modification Access' │ │ │ │ +B44E9 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B44ED Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B44F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B44F3 Length 000B (11) │ │ │ │ +B44F5 Version 01 (1) │ │ │ │ +B44F6 UID Size 04 (4) │ │ │ │ +B44F7 UID 00000000 (0) │ │ │ │ +B44FB GID Size 04 (4) │ │ │ │ +B44FC GID 00000000 (0) │ │ │ │ +B4500 PAYLOAD │ │ │ │ + │ │ │ │ +B6084 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +B6088 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B6089 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B608A General Purpose Flag 0000 (0) │ │ │ │ +B608C Compression Method 0000 (0) 'Stored' │ │ │ │ +B608E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B6092 CRC D0D71F86 (3503759238) │ │ │ │ +B6096 Compressed Size 00000B7B (2939) │ │ │ │ +B609A Uncompressed Size 00000B7B (2939) │ │ │ │ +B609E Filename Length 0016 (22) │ │ │ │ +B60A0 Extra Length 001C (28) │ │ │ │ +B60A2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB60A2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B60B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B60BA Length 0009 (9) │ │ │ │ +B60BC Flags 03 (3) 'Modification Access' │ │ │ │ +B60BD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B60C1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B60C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B60C7 Length 000B (11) │ │ │ │ +B60C9 Version 01 (1) │ │ │ │ +B60CA UID Size 04 (4) │ │ │ │ +B60CB UID 00000000 (0) │ │ │ │ +B60CF GID Size 04 (4) │ │ │ │ +B60D0 GID 00000000 (0) │ │ │ │ +B60D4 PAYLOAD │ │ │ │ + │ │ │ │ +B6C4F LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +B6C53 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B6C54 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B6C55 General Purpose Flag 0000 (0) │ │ │ │ +B6C57 Compression Method 0000 (0) 'Stored' │ │ │ │ +B6C59 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B6C5D CRC FFF9C4D2 (4294558930) │ │ │ │ +B6C61 Compressed Size 0000138F (5007) │ │ │ │ +B6C65 Uncompressed Size 0000138F (5007) │ │ │ │ +B6C69 Filename Length 0016 (22) │ │ │ │ +B6C6B Extra Length 001C (28) │ │ │ │ +B6C6D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB6C6D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B6C83 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B6C85 Length 0009 (9) │ │ │ │ +B6C87 Flags 03 (3) 'Modification Access' │ │ │ │ +B6C88 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B6C8C Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B6C90 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B6C92 Length 000B (11) │ │ │ │ +B6C94 Version 01 (1) │ │ │ │ +B6C95 UID Size 04 (4) │ │ │ │ +B6C96 UID 00000000 (0) │ │ │ │ +B6C9A GID Size 04 (4) │ │ │ │ +B6C9B GID 00000000 (0) │ │ │ │ +B6C9F PAYLOAD │ │ │ │ + │ │ │ │ +B802E LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +B8032 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B8033 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8034 General Purpose Flag 0000 (0) │ │ │ │ +B8036 Compression Method 0000 (0) 'Stored' │ │ │ │ +B8038 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B803C CRC A1037E8E (2701360782) │ │ │ │ +B8040 Compressed Size 0000145E (5214) │ │ │ │ +B8044 Uncompressed Size 0000145E (5214) │ │ │ │ +B8048 Filename Length 0016 (22) │ │ │ │ +B804A Extra Length 001C (28) │ │ │ │ +B804C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB804C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8062 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8064 Length 0009 (9) │ │ │ │ +B8066 Flags 03 (3) 'Modification Access' │ │ │ │ +B8067 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B806B Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B806F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8071 Length 000B (11) │ │ │ │ +B8073 Version 01 (1) │ │ │ │ +B8074 UID Size 04 (4) │ │ │ │ +B8075 UID 00000000 (0) │ │ │ │ +B8079 GID Size 04 (4) │ │ │ │ +B807A GID 00000000 (0) │ │ │ │ +B807E PAYLOAD │ │ │ │ + │ │ │ │ +B94DC LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ +B94E0 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B94E1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B94E2 General Purpose Flag 0000 (0) │ │ │ │ +B94E4 Compression Method 0000 (0) 'Stored' │ │ │ │ +B94E6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B94EA CRC 5E9E64F1 (1587438833) │ │ │ │ +B94EE Compressed Size 000008EC (2284) │ │ │ │ +B94F2 Uncompressed Size 000008EC (2284) │ │ │ │ +B94F6 Filename Length 0016 (22) │ │ │ │ +B94F8 Extra Length 001C (28) │ │ │ │ +B94FA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB94FA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9510 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9512 Length 0009 (9) │ │ │ │ +B9514 Flags 03 (3) 'Modification Access' │ │ │ │ +B9515 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B9519 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B951D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B951F Length 000B (11) │ │ │ │ +B9521 Version 01 (1) │ │ │ │ +B9522 UID Size 04 (4) │ │ │ │ +B9523 UID 00000000 (0) │ │ │ │ +B9527 GID Size 04 (4) │ │ │ │ +B9528 GID 00000000 (0) │ │ │ │ +B952C PAYLOAD │ │ │ │ + │ │ │ │ +B9E18 LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ +B9E1C Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B9E1D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9E1E General Purpose Flag 0000 (0) │ │ │ │ +B9E20 Compression Method 0000 (0) 'Stored' │ │ │ │ +B9E22 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +B9E26 CRC 42E340AB (1122189483) │ │ │ │ +B9E2A Compressed Size 00001F2E (7982) │ │ │ │ +B9E2E Uncompressed Size 00001F2E (7982) │ │ │ │ +B9E32 Filename Length 001E (30) │ │ │ │ +B9E34 Extra Length 001C (28) │ │ │ │ +B9E36 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9E36: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9E54 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9E56 Length 0009 (9) │ │ │ │ +B9E58 Flags 03 (3) 'Modification Access' │ │ │ │ +B9E59 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B9E5D Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +B9E61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9E63 Length 000B (11) │ │ │ │ +B9E65 Version 01 (1) │ │ │ │ +B9E66 UID Size 04 (4) │ │ │ │ +B9E67 UID 00000000 (0) │ │ │ │ +B9E6B GID Size 04 (4) │ │ │ │ +B9E6C GID 00000000 (0) │ │ │ │ +B9E70 PAYLOAD │ │ │ │ + │ │ │ │ +BBD9E LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ +BBDA2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BBDA3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BBDA4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BBDA6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BBDA8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +BBDAC CRC 28F7826D (687309421) │ │ │ │ +BBDB0 Compressed Size 00003D80 (15744) │ │ │ │ +BBDB4 Uncompressed Size 000166B0 (91824) │ │ │ │ +BBDB8 Filename Length 001A (26) │ │ │ │ +BBDBA Extra Length 001C (28) │ │ │ │ +BBDBC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBBDBC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BBDD6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BBDD8 Length 0009 (9) │ │ │ │ +BBDDA Flags 03 (3) 'Modification Access' │ │ │ │ +BBDDB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +BBDDF Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +BBDE3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BBDE5 Length 000B (11) │ │ │ │ +BBDE7 Version 01 (1) │ │ │ │ +BBDE8 UID Size 04 (4) │ │ │ │ +BBDE9 UID 00000000 (0) │ │ │ │ +BBDED GID Size 04 (4) │ │ │ │ +BBDEE GID 00000000 (0) │ │ │ │ +BBDF2 PAYLOAD │ │ │ │ + │ │ │ │ +BFB72 LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ +BFB76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BFB77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BFB78 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BFB7A Compression Method 0008 (8) 'Deflated' │ │ │ │ +BFB7C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +BFB80 CRC B0D0A5F7 (2966463991) │ │ │ │ +BFB84 Compressed Size 000029D0 (10704) │ │ │ │ +BFB88 Uncompressed Size 0000BB3A (47930) │ │ │ │ +BFB8C Filename Length 0018 (24) │ │ │ │ +BFB8E Extra Length 001C (28) │ │ │ │ +BFB90 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBFB90: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BFBA8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BFBAA Length 0009 (9) │ │ │ │ +BFBAC Flags 03 (3) 'Modification Access' │ │ │ │ +BFBAD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +BFBB1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +BFBB5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BFBB7 Length 000B (11) │ │ │ │ +BFBB9 Version 01 (1) │ │ │ │ +BFBBA UID Size 04 (4) │ │ │ │ +BFBBB UID 00000000 (0) │ │ │ │ +BFBBF GID Size 04 (4) │ │ │ │ +BFBC0 GID 00000000 (0) │ │ │ │ +BFBC4 PAYLOAD │ │ │ │ + │ │ │ │ +C2594 LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ +C2598 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2599 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C259A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C259C Compression Method 0008 (8) 'Deflated' │ │ │ │ +C259E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C25A2 CRC DCB3B516 (3702764822) │ │ │ │ +C25A6 Compressed Size 000000AE (174) │ │ │ │ +C25AA Uncompressed Size 000000FC (252) │ │ │ │ +C25AE Filename Length 0016 (22) │ │ │ │ +C25B0 Extra Length 001C (28) │ │ │ │ +C25B2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC25B2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C25C8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C25CA Length 0009 (9) │ │ │ │ +C25CC Flags 03 (3) 'Modification Access' │ │ │ │ +C25CD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C25D1 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C25D5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C25D7 Length 000B (11) │ │ │ │ +C25D9 Version 01 (1) │ │ │ │ +C25DA UID Size 04 (4) │ │ │ │ +C25DB UID 00000000 (0) │ │ │ │ +C25DF GID Size 04 (4) │ │ │ │ +C25E0 GID 00000000 (0) │ │ │ │ +C25E4 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -C2684 LOCAL HEADER #97 04034B50 (67324752) │ │ │ │ -C2688 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2689 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C268A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C268C Compression Method 0008 (8) 'Deflated' │ │ │ │ -C268E Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C2692 CRC 58439733 (1480824627) │ │ │ │ -C2696 Compressed Size 00000077 (119) │ │ │ │ -C269A Uncompressed Size 000000A2 (162) │ │ │ │ -C269E Filename Length 002D (45) │ │ │ │ -C26A0 Extra Length 001C (28) │ │ │ │ -C26A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC26A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C26CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C26D1 Length 0009 (9) │ │ │ │ -C26D3 Flags 03 (3) 'Modification Access' │ │ │ │ -C26D4 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C26D8 Access Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C26DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C26DE Length 000B (11) │ │ │ │ -C26E0 Version 01 (1) │ │ │ │ -C26E1 UID Size 04 (4) │ │ │ │ -C26E2 UID 00000000 (0) │ │ │ │ -C26E6 GID Size 04 (4) │ │ │ │ -C26E7 GID 00000000 (0) │ │ │ │ -C26EB PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -C2762 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -C2766 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2767 Created OS 03 (3) 'Unix' │ │ │ │ -C2768 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C2769 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C276A General Purpose Flag 0000 (0) │ │ │ │ -C276C Compression Method 0000 (0) 'Stored' │ │ │ │ -C276E Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2772 CRC 2CAB616F (749429103) │ │ │ │ -C2776 Compressed Size 00000014 (20) │ │ │ │ -C277A Uncompressed Size 00000014 (20) │ │ │ │ -C277E Filename Length 0008 (8) │ │ │ │ -C2780 Extra Length 0018 (24) │ │ │ │ -C2782 Comment Length 0000 (0) │ │ │ │ -C2784 Disk Start 0000 (0) │ │ │ │ -C2786 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2788 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C278C Local Header Offset 00000000 (0) │ │ │ │ -C2790 Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2790: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2798 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C279A Length 0005 (5) │ │ │ │ -C279C Flags 01 (1) 'Modification' │ │ │ │ -C279D Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C27A1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C27A3 Length 000B (11) │ │ │ │ -C27A5 Version 01 (1) │ │ │ │ -C27A6 UID Size 04 (4) │ │ │ │ -C27A7 UID 00000000 (0) │ │ │ │ -C27AB GID Size 04 (4) │ │ │ │ -C27AC GID 00000000 (0) │ │ │ │ - │ │ │ │ -C27B0 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -C27B4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C27B5 Created OS 03 (3) 'Unix' │ │ │ │ -C27B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C27B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C27B8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C27BA Compression Method 0008 (8) 'Deflated' │ │ │ │ -C27BC Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C27C0 CRC 29F05690 (703616656) │ │ │ │ -C27C4 Compressed Size 00000D24 (3364) │ │ │ │ -C27C8 Uncompressed Size 00003933 (14643) │ │ │ │ -C27CC Filename Length 001B (27) │ │ │ │ -C27CE Extra Length 0018 (24) │ │ │ │ -C27D0 Comment Length 0000 (0) │ │ │ │ -C27D2 Disk Start 0000 (0) │ │ │ │ -C27D4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C27D6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C27DA Local Header Offset 00000056 (86) │ │ │ │ -C27DE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC27DE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C27F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C27FB Length 0005 (5) │ │ │ │ -C27FD Flags 01 (1) 'Modification' │ │ │ │ -C27FE Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2802 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2804 Length 000B (11) │ │ │ │ -C2806 Version 01 (1) │ │ │ │ -C2807 UID Size 04 (4) │ │ │ │ -C2808 UID 00000000 (0) │ │ │ │ -C280C GID Size 04 (4) │ │ │ │ -C280D GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2811 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -C2815 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2816 Created OS 03 (3) 'Unix' │ │ │ │ -C2817 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2818 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2819 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C281B Compression Method 0008 (8) 'Deflated' │ │ │ │ -C281D Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2821 CRC 546A35FD (1416246781) │ │ │ │ -C2825 Compressed Size 000015B1 (5553) │ │ │ │ -C2829 Uncompressed Size 00004605 (17925) │ │ │ │ -C282D Filename Length 0014 (20) │ │ │ │ -C282F Extra Length 0018 (24) │ │ │ │ -C2831 Comment Length 0000 (0) │ │ │ │ -C2833 Disk Start 0000 (0) │ │ │ │ -C2835 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2837 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C283B Local Header Offset 00000DCF (3535) │ │ │ │ -C283F Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC283F: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2853 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2855 Length 0005 (5) │ │ │ │ -C2857 Flags 01 (1) 'Modification' │ │ │ │ -C2858 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C285C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C285E Length 000B (11) │ │ │ │ -C2860 Version 01 (1) │ │ │ │ -C2861 UID Size 04 (4) │ │ │ │ -C2862 UID 00000000 (0) │ │ │ │ -C2866 GID Size 04 (4) │ │ │ │ -C2867 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C286B CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -C286F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2870 Created OS 03 (3) 'Unix' │ │ │ │ -C2871 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2872 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2873 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2875 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2877 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C287B CRC 03A4A46E (61121646) │ │ │ │ -C287F Compressed Size 000006D4 (1748) │ │ │ │ -C2883 Uncompressed Size 00001241 (4673) │ │ │ │ -C2887 Filename Length 0013 (19) │ │ │ │ -C2889 Extra Length 0018 (24) │ │ │ │ -C288B Comment Length 0000 (0) │ │ │ │ -C288D Disk Start 0000 (0) │ │ │ │ -C288F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2891 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2895 Local Header Offset 000023CE (9166) │ │ │ │ -C2899 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2899: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C28AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C28AE Length 0005 (5) │ │ │ │ -C28B0 Flags 01 (1) 'Modification' │ │ │ │ -C28B1 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C28B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C28B7 Length 000B (11) │ │ │ │ -C28B9 Version 01 (1) │ │ │ │ -C28BA UID Size 04 (4) │ │ │ │ -C28BB UID 00000000 (0) │ │ │ │ -C28BF GID Size 04 (4) │ │ │ │ -C28C0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C28C4 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -C28C8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C28C9 Created OS 03 (3) 'Unix' │ │ │ │ -C28CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C28CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C28CC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C28CE Compression Method 0008 (8) 'Deflated' │ │ │ │ -C28D0 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C28D4 CRC 404477C9 (1078228937) │ │ │ │ -C28D8 Compressed Size 00002E75 (11893) │ │ │ │ -C28DC Uncompressed Size 0000D4C1 (54465) │ │ │ │ -C28E0 Filename Length 0014 (20) │ │ │ │ -C28E2 Extra Length 0018 (24) │ │ │ │ -C28E4 Comment Length 0000 (0) │ │ │ │ -C28E6 Disk Start 0000 (0) │ │ │ │ -C28E8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C28EA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C28EE Local Header Offset 00002AEF (10991) │ │ │ │ -C28F2 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC28F2: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2906 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2908 Length 0005 (5) │ │ │ │ -C290A Flags 01 (1) 'Modification' │ │ │ │ -C290B Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C290F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2911 Length 000B (11) │ │ │ │ -C2913 Version 01 (1) │ │ │ │ -C2914 UID Size 04 (4) │ │ │ │ -C2915 UID 00000000 (0) │ │ │ │ -C2919 GID Size 04 (4) │ │ │ │ -C291A GID 00000000 (0) │ │ │ │ - │ │ │ │ -C291E CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -C2922 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2923 Created OS 03 (3) 'Unix' │ │ │ │ -C2924 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2925 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2926 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2928 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C292A Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C292E CRC 272DD323 (657314595) │ │ │ │ -C2932 Compressed Size 000003EF (1007) │ │ │ │ -C2936 Uncompressed Size 00000876 (2166) │ │ │ │ -C293A Filename Length 0014 (20) │ │ │ │ -C293C Extra Length 0018 (24) │ │ │ │ -C293E Comment Length 0000 (0) │ │ │ │ -C2940 Disk Start 0000 (0) │ │ │ │ -C2942 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2944 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2948 Local Header Offset 000059B2 (22962) │ │ │ │ -C294C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC294C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2960 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2962 Length 0005 (5) │ │ │ │ -C2964 Flags 01 (1) 'Modification' │ │ │ │ -C2965 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2969 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C296B Length 000B (11) │ │ │ │ -C296D Version 01 (1) │ │ │ │ -C296E UID Size 04 (4) │ │ │ │ -C296F UID 00000000 (0) │ │ │ │ -C2973 GID Size 04 (4) │ │ │ │ -C2974 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2978 CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -C297C Created Zip Spec 3D (61) '6.1' │ │ │ │ -C297D Created OS 03 (3) 'Unix' │ │ │ │ -C297E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C297F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2980 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2982 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2984 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2988 CRC 2F07766F (789018223) │ │ │ │ -C298C Compressed Size 000001AD (429) │ │ │ │ -C2990 Uncompressed Size 000002FC (764) │ │ │ │ -C2994 Filename Length 0011 (17) │ │ │ │ -C2996 Extra Length 0018 (24) │ │ │ │ -C2998 Comment Length 0000 (0) │ │ │ │ -C299A Disk Start 0000 (0) │ │ │ │ -C299C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C299E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C29A2 Local Header Offset 00005DEF (24047) │ │ │ │ -C29A6 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC29A6: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C29B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C29B9 Length 0005 (5) │ │ │ │ -C29BB Flags 01 (1) 'Modification' │ │ │ │ -C29BC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C29C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C29C2 Length 000B (11) │ │ │ │ -C29C4 Version 01 (1) │ │ │ │ -C29C5 UID Size 04 (4) │ │ │ │ -C29C6 UID 00000000 (0) │ │ │ │ -C29CA GID Size 04 (4) │ │ │ │ -C29CB GID 00000000 (0) │ │ │ │ - │ │ │ │ -C29CF CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -C29D3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C29D4 Created OS 03 (3) 'Unix' │ │ │ │ -C29D5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C29D6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C29D7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C29D9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C29DB Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C29DF CRC AD55D2AE (2908082862) │ │ │ │ -C29E3 Compressed Size 000020BE (8382) │ │ │ │ -C29E7 Uncompressed Size 0000B4B1 (46257) │ │ │ │ -C29EB Filename Length 001B (27) │ │ │ │ -C29ED Extra Length 0018 (24) │ │ │ │ -C29EF Comment Length 0000 (0) │ │ │ │ -C29F1 Disk Start 0000 (0) │ │ │ │ -C29F3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C29F5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C29F9 Local Header Offset 00005FE7 (24551) │ │ │ │ -C29FD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC29FD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2A18 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2A1A Length 0005 (5) │ │ │ │ -C2A1C Flags 01 (1) 'Modification' │ │ │ │ -C2A1D Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2A21 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2A23 Length 000B (11) │ │ │ │ -C2A25 Version 01 (1) │ │ │ │ -C2A26 UID Size 04 (4) │ │ │ │ -C2A27 UID 00000000 (0) │ │ │ │ -C2A2B GID Size 04 (4) │ │ │ │ -C2A2C GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2A30 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -C2A34 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2A35 Created OS 03 (3) 'Unix' │ │ │ │ -C2A36 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2A37 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2A38 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2A3A Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2A3C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2A40 CRC 4E72FA06 (1316157958) │ │ │ │ -C2A44 Compressed Size 00000E68 (3688) │ │ │ │ -C2A48 Uncompressed Size 00003097 (12439) │ │ │ │ -C2A4C Filename Length 001D (29) │ │ │ │ -C2A4E Extra Length 0018 (24) │ │ │ │ -C2A50 Comment Length 0000 (0) │ │ │ │ -C2A52 Disk Start 0000 (0) │ │ │ │ -C2A54 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2A56 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2A5A Local Header Offset 000080FA (33018) │ │ │ │ -C2A5E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2A5E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2A7B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2A7D Length 0005 (5) │ │ │ │ -C2A7F Flags 01 (1) 'Modification' │ │ │ │ -C2A80 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2A84 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2A86 Length 000B (11) │ │ │ │ -C2A88 Version 01 (1) │ │ │ │ -C2A89 UID Size 04 (4) │ │ │ │ -C2A8A UID 00000000 (0) │ │ │ │ -C2A8E GID Size 04 (4) │ │ │ │ -C2A8F GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2A93 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -C2A97 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2A98 Created OS 03 (3) 'Unix' │ │ │ │ -C2A99 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2A9A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2A9B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2A9D Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2A9F Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2AA3 CRC F138337A (4046992250) │ │ │ │ -C2AA7 Compressed Size 0000098E (2446) │ │ │ │ -C2AAB Uncompressed Size 00001D39 (7481) │ │ │ │ -C2AAF Filename Length 0019 (25) │ │ │ │ -C2AB1 Extra Length 0018 (24) │ │ │ │ -C2AB3 Comment Length 0000 (0) │ │ │ │ -C2AB5 Disk Start 0000 (0) │ │ │ │ -C2AB7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2AB9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2ABD Local Header Offset 00008FB9 (36793) │ │ │ │ -C2AC1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2AC1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2ADA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2ADC Length 0005 (5) │ │ │ │ -C2ADE Flags 01 (1) 'Modification' │ │ │ │ -C2ADF Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2AE3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2AE5 Length 000B (11) │ │ │ │ -C2AE7 Version 01 (1) │ │ │ │ -C2AE8 UID Size 04 (4) │ │ │ │ -C2AE9 UID 00000000 (0) │ │ │ │ -C2AED GID Size 04 (4) │ │ │ │ -C2AEE GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2AF2 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -C2AF6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2AF7 Created OS 03 (3) 'Unix' │ │ │ │ -C2AF8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2AF9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2AFA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2AFC Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2AFE Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2B02 CRC FC4F496F (4233054575) │ │ │ │ -C2B06 Compressed Size 00003883 (14467) │ │ │ │ -C2B0A Uncompressed Size 0000F81F (63519) │ │ │ │ -C2B0E Filename Length 0015 (21) │ │ │ │ -C2B10 Extra Length 0018 (24) │ │ │ │ -C2B12 Comment Length 0000 (0) │ │ │ │ -C2B14 Disk Start 0000 (0) │ │ │ │ -C2B16 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2B18 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2B1C Local Header Offset 0000999A (39322) │ │ │ │ -C2B20 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2B20: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2B35 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2B37 Length 0005 (5) │ │ │ │ -C2B39 Flags 01 (1) 'Modification' │ │ │ │ -C2B3A Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2B3E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2B40 Length 000B (11) │ │ │ │ -C2B42 Version 01 (1) │ │ │ │ -C2B43 UID Size 04 (4) │ │ │ │ -C2B44 UID 00000000 (0) │ │ │ │ -C2B48 GID Size 04 (4) │ │ │ │ -C2B49 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2B4D CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -C2B51 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2B52 Created OS 03 (3) 'Unix' │ │ │ │ -C2B53 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2B54 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2B55 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2B57 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2B59 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2B5D CRC 0BB598F2 (196450546) │ │ │ │ -C2B61 Compressed Size 0000AAFA (43770) │ │ │ │ -C2B65 Uncompressed Size 0003E0D8 (254168) │ │ │ │ -C2B69 Filename Length 0012 (18) │ │ │ │ -C2B6B Extra Length 0018 (24) │ │ │ │ -C2B6D Comment Length 0000 (0) │ │ │ │ -C2B6F Disk Start 0000 (0) │ │ │ │ -C2B71 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2B73 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2B77 Local Header Offset 0000D26C (53868) │ │ │ │ -C2B7B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2B7B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2B8D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2B8F Length 0005 (5) │ │ │ │ -C2B91 Flags 01 (1) 'Modification' │ │ │ │ -C2B92 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2B96 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2B98 Length 000B (11) │ │ │ │ -C2B9A Version 01 (1) │ │ │ │ -C2B9B UID Size 04 (4) │ │ │ │ -C2B9C UID 00000000 (0) │ │ │ │ -C2BA0 GID Size 04 (4) │ │ │ │ -C2BA1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2BA5 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -C2BA9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2BAA Created OS 03 (3) 'Unix' │ │ │ │ -C2BAB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2BAC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2BAD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2BAF Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2BB1 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2BB5 CRC C50A848B (3305800843) │ │ │ │ -C2BB9 Compressed Size 00003AF8 (15096) │ │ │ │ -C2BBD Uncompressed Size 0001B421 (111649) │ │ │ │ -C2BC1 Filename Length 0015 (21) │ │ │ │ -C2BC3 Extra Length 0018 (24) │ │ │ │ -C2BC5 Comment Length 0000 (0) │ │ │ │ -C2BC7 Disk Start 0000 (0) │ │ │ │ -C2BC9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2BCB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2BCF Local Header Offset 00017DB2 (97714) │ │ │ │ -C2BD3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2BD3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2BE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2BEA Length 0005 (5) │ │ │ │ -C2BEC Flags 01 (1) 'Modification' │ │ │ │ -C2BED Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2BF1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2BF3 Length 000B (11) │ │ │ │ -C2BF5 Version 01 (1) │ │ │ │ -C2BF6 UID Size 04 (4) │ │ │ │ -C2BF7 UID 00000000 (0) │ │ │ │ -C2BFB GID Size 04 (4) │ │ │ │ -C2BFC GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2C00 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -C2C04 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2C05 Created OS 03 (3) 'Unix' │ │ │ │ -C2C06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2C07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2C08 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2C0A Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2C0C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2C10 CRC B6C2DA9C (3066223260) │ │ │ │ -C2C14 Compressed Size 000091C5 (37317) │ │ │ │ -C2C18 Uncompressed Size 0003DBD1 (252881) │ │ │ │ -C2C1C Filename Length 0014 (20) │ │ │ │ -C2C1E Extra Length 0018 (24) │ │ │ │ -C2C20 Comment Length 0000 (0) │ │ │ │ -C2C22 Disk Start 0000 (0) │ │ │ │ -C2C24 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2C26 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2C2A Local Header Offset 0001B8F9 (112889) │ │ │ │ -C2C2E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2C2E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2C42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2C44 Length 0005 (5) │ │ │ │ -C2C46 Flags 01 (1) 'Modification' │ │ │ │ -C2C47 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2C4B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2C4D Length 000B (11) │ │ │ │ -C2C4F Version 01 (1) │ │ │ │ -C2C50 UID Size 04 (4) │ │ │ │ -C2C51 UID 00000000 (0) │ │ │ │ -C2C55 GID Size 04 (4) │ │ │ │ -C2C56 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2C5A CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -C2C5E Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2C5F Created OS 03 (3) 'Unix' │ │ │ │ -C2C60 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2C61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2C62 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2C64 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2C66 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2C6A CRC 6E4D0A9B (1850542747) │ │ │ │ -C2C6E Compressed Size 00009BA8 (39848) │ │ │ │ -C2C72 Uncompressed Size 00027CF5 (163061) │ │ │ │ -C2C76 Filename Length 0019 (25) │ │ │ │ -C2C78 Extra Length 0018 (24) │ │ │ │ -C2C7A Comment Length 0000 (0) │ │ │ │ -C2C7C Disk Start 0000 (0) │ │ │ │ -C2C7E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2C80 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2C84 Local Header Offset 00024B0C (150284) │ │ │ │ -C2C88 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2C88: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2CA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2CA3 Length 0005 (5) │ │ │ │ -C2CA5 Flags 01 (1) 'Modification' │ │ │ │ -C2CA6 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2CAA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2CAC Length 000B (11) │ │ │ │ -C2CAE Version 01 (1) │ │ │ │ -C2CAF UID Size 04 (4) │ │ │ │ -C2CB0 UID 00000000 (0) │ │ │ │ -C2CB4 GID Size 04 (4) │ │ │ │ -C2CB5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2CB9 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -C2CBD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2CBE Created OS 03 (3) 'Unix' │ │ │ │ -C2CBF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2CC0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2CC1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2CC3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2CC5 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2CC9 CRC 36D4AE72 (919907954) │ │ │ │ -C2CCD Compressed Size 00001219 (4633) │ │ │ │ -C2CD1 Uncompressed Size 00003C91 (15505) │ │ │ │ -C2CD5 Filename Length 0010 (16) │ │ │ │ -C2CD7 Extra Length 0018 (24) │ │ │ │ -C2CD9 Comment Length 0000 (0) │ │ │ │ -C2CDB Disk Start 0000 (0) │ │ │ │ -C2CDD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2CDF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2CE3 Local Header Offset 0002E707 (190215) │ │ │ │ -C2CE7 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2CE7: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2CF7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2CF9 Length 0005 (5) │ │ │ │ -C2CFB Flags 01 (1) 'Modification' │ │ │ │ -C2CFC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2D00 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2D02 Length 000B (11) │ │ │ │ -C2D04 Version 01 (1) │ │ │ │ -C2D05 UID Size 04 (4) │ │ │ │ -C2D06 UID 00000000 (0) │ │ │ │ -C2D0A GID Size 04 (4) │ │ │ │ -C2D0B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2D0F CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -C2D13 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2D14 Created OS 03 (3) 'Unix' │ │ │ │ -C2D15 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2D16 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2D17 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2D19 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2D1B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2D1F CRC 0BA4F9CD (195361229) │ │ │ │ -C2D23 Compressed Size 00002A5F (10847) │ │ │ │ -C2D27 Uncompressed Size 000113A7 (70567) │ │ │ │ -C2D2B Filename Length 0016 (22) │ │ │ │ -C2D2D Extra Length 0018 (24) │ │ │ │ -C2D2F Comment Length 0000 (0) │ │ │ │ -C2D31 Disk Start 0000 (0) │ │ │ │ -C2D33 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2D35 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2D39 Local Header Offset 0002F96A (194922) │ │ │ │ -C2D3D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2D3D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2D53 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2D55 Length 0005 (5) │ │ │ │ -C2D57 Flags 01 (1) 'Modification' │ │ │ │ -C2D58 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2D5C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2D5E Length 000B (11) │ │ │ │ -C2D60 Version 01 (1) │ │ │ │ -C2D61 UID Size 04 (4) │ │ │ │ -C2D62 UID 00000000 (0) │ │ │ │ -C2D66 GID Size 04 (4) │ │ │ │ -C2D67 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2D6B CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -C2D6F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2D70 Created OS 03 (3) 'Unix' │ │ │ │ -C2D71 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2D72 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2D73 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2D75 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2D77 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2D7B CRC AF0A8C6F (2936704111) │ │ │ │ -C2D7F Compressed Size 000014D9 (5337) │ │ │ │ -C2D83 Uncompressed Size 0000518D (20877) │ │ │ │ -C2D87 Filename Length 001D (29) │ │ │ │ -C2D89 Extra Length 0018 (24) │ │ │ │ -C2D8B Comment Length 0000 (0) │ │ │ │ -C2D8D Disk Start 0000 (0) │ │ │ │ -C2D8F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2D91 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2D95 Local Header Offset 00032419 (205849) │ │ │ │ -C2D99 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2D99: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2DB6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2DB8 Length 0005 (5) │ │ │ │ -C2DBA Flags 01 (1) 'Modification' │ │ │ │ -C2DBB Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2DBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2DC1 Length 000B (11) │ │ │ │ -C2DC3 Version 01 (1) │ │ │ │ -C2DC4 UID Size 04 (4) │ │ │ │ -C2DC5 UID 00000000 (0) │ │ │ │ -C2DC9 GID Size 04 (4) │ │ │ │ -C2DCA GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2DCE CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -C2DD2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2DD3 Created OS 03 (3) 'Unix' │ │ │ │ -C2DD4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2DD5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2DD6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2DD8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2DDA Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2DDE CRC 0B59709A (190410906) │ │ │ │ -C2DE2 Compressed Size 000037FF (14335) │ │ │ │ -C2DE6 Uncompressed Size 0000EA4B (59979) │ │ │ │ -C2DEA Filename Length 001C (28) │ │ │ │ -C2DEC Extra Length 0018 (24) │ │ │ │ -C2DEE Comment Length 0000 (0) │ │ │ │ -C2DF0 Disk Start 0000 (0) │ │ │ │ -C2DF2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2DF4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2DF8 Local Header Offset 00033949 (211273) │ │ │ │ -C2DFC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2DFC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2E18 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2E1A Length 0005 (5) │ │ │ │ -C2E1C Flags 01 (1) 'Modification' │ │ │ │ -C2E1D Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2E21 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2E23 Length 000B (11) │ │ │ │ -C2E25 Version 01 (1) │ │ │ │ -C2E26 UID Size 04 (4) │ │ │ │ -C2E27 UID 00000000 (0) │ │ │ │ -C2E2B GID Size 04 (4) │ │ │ │ -C2E2C GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2E30 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -C2E34 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2E35 Created OS 03 (3) 'Unix' │ │ │ │ -C2E36 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2E37 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2E38 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2E3A Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2E3C Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2E40 CRC 958EC234 (2509161012) │ │ │ │ -C2E44 Compressed Size 0000069E (1694) │ │ │ │ -C2E48 Uncompressed Size 000011F3 (4595) │ │ │ │ -C2E4C Filename Length 001C (28) │ │ │ │ -C2E4E Extra Length 0018 (24) │ │ │ │ -C2E50 Comment Length 0000 (0) │ │ │ │ -C2E52 Disk Start 0000 (0) │ │ │ │ -C2E54 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2E56 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2E5A Local Header Offset 0003719E (225694) │ │ │ │ -C2E5E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2E5E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2E7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2E7C Length 0005 (5) │ │ │ │ -C2E7E Flags 01 (1) 'Modification' │ │ │ │ -C2E7F Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2E83 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2E85 Length 000B (11) │ │ │ │ -C2E87 Version 01 (1) │ │ │ │ -C2E88 UID Size 04 (4) │ │ │ │ -C2E89 UID 00000000 (0) │ │ │ │ -C2E8D GID Size 04 (4) │ │ │ │ -C2E8E GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2E92 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -C2E96 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2E97 Created OS 03 (3) 'Unix' │ │ │ │ -C2E98 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2E99 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2E9A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2E9C Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2E9E Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2EA2 CRC 42501F7E (1112547198) │ │ │ │ -C2EA6 Compressed Size 0000107D (4221) │ │ │ │ -C2EAA Uncompressed Size 00004BFE (19454) │ │ │ │ -C2EAE Filename Length 001B (27) │ │ │ │ -C2EB0 Extra Length 0018 (24) │ │ │ │ -C2EB2 Comment Length 0000 (0) │ │ │ │ -C2EB4 Disk Start 0000 (0) │ │ │ │ -C2EB6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2EB8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2EBC Local Header Offset 00037892 (227474) │ │ │ │ -C2EC0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2EC0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2EDB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2EDD Length 0005 (5) │ │ │ │ -C2EDF Flags 01 (1) 'Modification' │ │ │ │ -C2EE0 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2EE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2EE6 Length 000B (11) │ │ │ │ -C2EE8 Version 01 (1) │ │ │ │ -C2EE9 UID Size 04 (4) │ │ │ │ -C2EEA UID 00000000 (0) │ │ │ │ -C2EEE GID Size 04 (4) │ │ │ │ -C2EEF GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2EF3 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -C2EF7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2EF8 Created OS 03 (3) 'Unix' │ │ │ │ -C2EF9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2EFA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2EFB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2EFD Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2EFF Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2F03 CRC 3D4A57FE (1028282366) │ │ │ │ -C2F07 Compressed Size 00003B3B (15163) │ │ │ │ -C2F0B Uncompressed Size 0000D491 (54417) │ │ │ │ -C2F0F Filename Length 001D (29) │ │ │ │ -C2F11 Extra Length 0018 (24) │ │ │ │ -C2F13 Comment Length 0000 (0) │ │ │ │ -C2F15 Disk Start 0000 (0) │ │ │ │ -C2F17 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2F19 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2F1D Local Header Offset 00038964 (231780) │ │ │ │ -C2F21 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2F21: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2F3E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2F40 Length 0005 (5) │ │ │ │ -C2F42 Flags 01 (1) 'Modification' │ │ │ │ -C2F43 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2F47 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2F49 Length 000B (11) │ │ │ │ -C2F4B Version 01 (1) │ │ │ │ -C2F4C UID Size 04 (4) │ │ │ │ -C2F4D UID 00000000 (0) │ │ │ │ -C2F51 GID Size 04 (4) │ │ │ │ -C2F52 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2F56 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -C2F5A Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2F5B Created OS 03 (3) 'Unix' │ │ │ │ -C2F5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2F5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2F5E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2F60 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2F62 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2F66 CRC 76464E51 (1984319057) │ │ │ │ -C2F6A Compressed Size 00000D6B (3435) │ │ │ │ -C2F6E Uncompressed Size 0000388A (14474) │ │ │ │ -C2F72 Filename Length 001D (29) │ │ │ │ -C2F74 Extra Length 0018 (24) │ │ │ │ -C2F76 Comment Length 0000 (0) │ │ │ │ -C2F78 Disk Start 0000 (0) │ │ │ │ -C2F7A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2F7C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2F80 Local Header Offset 0003C4F6 (247030) │ │ │ │ -C2F84 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2F84: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C2FA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C2FA3 Length 0005 (5) │ │ │ │ -C2FA5 Flags 01 (1) 'Modification' │ │ │ │ -C2FA6 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C2FAA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C2FAC Length 000B (11) │ │ │ │ -C2FAE Version 01 (1) │ │ │ │ -C2FAF UID Size 04 (4) │ │ │ │ -C2FB0 UID 00000000 (0) │ │ │ │ -C2FB4 GID Size 04 (4) │ │ │ │ -C2FB5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C2FB9 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -C2FBD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C2FBE Created OS 03 (3) 'Unix' │ │ │ │ -C2FBF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C2FC0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C2FC1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C2FC3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C2FC5 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C2FC9 CRC 50EF047D (1357841533) │ │ │ │ -C2FCD Compressed Size 00001C86 (7302) │ │ │ │ -C2FD1 Uncompressed Size 0000C038 (49208) │ │ │ │ -C2FD5 Filename Length 001A (26) │ │ │ │ -C2FD7 Extra Length 0018 (24) │ │ │ │ -C2FD9 Comment Length 0000 (0) │ │ │ │ -C2FDB Disk Start 0000 (0) │ │ │ │ -C2FDD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C2FDF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C2FE3 Local Header Offset 0003D2B8 (250552) │ │ │ │ -C2FE7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC2FE7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3001 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3003 Length 0005 (5) │ │ │ │ -C3005 Flags 01 (1) 'Modification' │ │ │ │ -C3006 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C300A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C300C Length 000B (11) │ │ │ │ -C300E Version 01 (1) │ │ │ │ -C300F UID Size 04 (4) │ │ │ │ -C3010 UID 00000000 (0) │ │ │ │ -C3014 GID Size 04 (4) │ │ │ │ -C3015 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3019 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -C301D Created Zip Spec 3D (61) '6.1' │ │ │ │ -C301E Created OS 03 (3) 'Unix' │ │ │ │ -C301F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3020 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3021 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3023 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3025 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3029 CRC 478B6B96 (1200319382) │ │ │ │ -C302D Compressed Size 000003DF (991) │ │ │ │ -C3031 Uncompressed Size 00000935 (2357) │ │ │ │ -C3035 Filename Length 0012 (18) │ │ │ │ -C3037 Extra Length 0018 (24) │ │ │ │ -C3039 Comment Length 0000 (0) │ │ │ │ -C303B Disk Start 0000 (0) │ │ │ │ -C303D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C303F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3043 Local Header Offset 0003EF92 (257938) │ │ │ │ -C3047 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3047: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3059 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C305B Length 0005 (5) │ │ │ │ -C305D Flags 01 (1) 'Modification' │ │ │ │ -C305E Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3062 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3064 Length 000B (11) │ │ │ │ -C3066 Version 01 (1) │ │ │ │ -C3067 UID Size 04 (4) │ │ │ │ -C3068 UID 00000000 (0) │ │ │ │ -C306C GID Size 04 (4) │ │ │ │ -C306D GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3071 CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -C3075 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3076 Created OS 03 (3) 'Unix' │ │ │ │ -C3077 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3078 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3079 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C307B Compression Method 0008 (8) 'Deflated' │ │ │ │ -C307D Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3081 CRC E79489F0 (3885271536) │ │ │ │ -C3085 Compressed Size 000001D3 (467) │ │ │ │ -C3089 Uncompressed Size 00000311 (785) │ │ │ │ -C308D Filename Length 0020 (32) │ │ │ │ -C308F Extra Length 0018 (24) │ │ │ │ -C3091 Comment Length 0000 (0) │ │ │ │ -C3093 Disk Start 0000 (0) │ │ │ │ -C3095 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3097 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C309B Local Header Offset 0003F3BD (259005) │ │ │ │ -C309F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC309F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C30BF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C30C1 Length 0005 (5) │ │ │ │ -C30C3 Flags 01 (1) 'Modification' │ │ │ │ -C30C4 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C30C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C30CA Length 000B (11) │ │ │ │ -C30CC Version 01 (1) │ │ │ │ -C30CD UID Size 04 (4) │ │ │ │ -C30CE UID 00000000 (0) │ │ │ │ -C30D2 GID Size 04 (4) │ │ │ │ -C30D3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C30D7 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -C30DB Created Zip Spec 3D (61) '6.1' │ │ │ │ -C30DC Created OS 03 (3) 'Unix' │ │ │ │ -C30DD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C30DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C30DF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C30E1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C30E3 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C30E7 CRC BB37392E (3140958510) │ │ │ │ -C30EB Compressed Size 000017AE (6062) │ │ │ │ -C30EF Uncompressed Size 00009D1B (40219) │ │ │ │ -C30F3 Filename Length 001B (27) │ │ │ │ -C30F5 Extra Length 0018 (24) │ │ │ │ -C30F7 Comment Length 0000 (0) │ │ │ │ -C30F9 Disk Start 0000 (0) │ │ │ │ -C30FB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C30FD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3101 Local Header Offset 0003F5EA (259562) │ │ │ │ -C3105 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3105: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3120 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3122 Length 0005 (5) │ │ │ │ -C3124 Flags 01 (1) 'Modification' │ │ │ │ -C3125 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3129 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C312B Length 000B (11) │ │ │ │ -C312D Version 01 (1) │ │ │ │ -C312E UID Size 04 (4) │ │ │ │ -C312F UID 00000000 (0) │ │ │ │ -C3133 GID Size 04 (4) │ │ │ │ -C3134 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3138 CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -C313C Created Zip Spec 3D (61) '6.1' │ │ │ │ -C313D Created OS 03 (3) 'Unix' │ │ │ │ -C313E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C313F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3140 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3142 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3144 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3148 CRC 09EB9A96 (166435478) │ │ │ │ -C314C Compressed Size 0000136D (4973) │ │ │ │ -C3150 Uncompressed Size 00003B58 (15192) │ │ │ │ -C3154 Filename Length 0015 (21) │ │ │ │ -C3156 Extra Length 0018 (24) │ │ │ │ -C3158 Comment Length 0000 (0) │ │ │ │ -C315A Disk Start 0000 (0) │ │ │ │ -C315C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C315E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3162 Local Header Offset 00040DED (265709) │ │ │ │ -C3166 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3166: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C317B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C317D Length 0005 (5) │ │ │ │ -C317F Flags 01 (1) 'Modification' │ │ │ │ -C3180 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3184 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3186 Length 000B (11) │ │ │ │ -C3188 Version 01 (1) │ │ │ │ -C3189 UID Size 04 (4) │ │ │ │ -C318A UID 00000000 (0) │ │ │ │ -C318E GID Size 04 (4) │ │ │ │ -C318F GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3193 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -C3197 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3198 Created OS 03 (3) 'Unix' │ │ │ │ -C3199 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C319A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C319B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C319D Compression Method 0008 (8) 'Deflated' │ │ │ │ -C319F Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C31A3 CRC F9EDEEDB (4193119963) │ │ │ │ -C31A7 Compressed Size 00000AC9 (2761) │ │ │ │ -C31AB Uncompressed Size 00002133 (8499) │ │ │ │ -C31AF Filename Length 0011 (17) │ │ │ │ -C31B1 Extra Length 0018 (24) │ │ │ │ -C31B3 Comment Length 0000 (0) │ │ │ │ -C31B5 Disk Start 0000 (0) │ │ │ │ -C31B7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C31B9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C31BD Local Header Offset 000421A9 (270761) │ │ │ │ -C31C1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC31C1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C31D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C31D4 Length 0005 (5) │ │ │ │ -C31D6 Flags 01 (1) 'Modification' │ │ │ │ -C31D7 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C31DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C31DD Length 000B (11) │ │ │ │ -C31DF Version 01 (1) │ │ │ │ -C31E0 UID Size 04 (4) │ │ │ │ -C31E1 UID 00000000 (0) │ │ │ │ -C31E5 GID Size 04 (4) │ │ │ │ -C31E6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C31EA CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -C31EE Created Zip Spec 3D (61) '6.1' │ │ │ │ -C31EF Created OS 03 (3) 'Unix' │ │ │ │ -C31F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C31F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C31F2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C31F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C31F6 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C31FA CRC 061A4CFE (102386942) │ │ │ │ -C31FE Compressed Size 000003FD (1021) │ │ │ │ -C3202 Uncompressed Size 00000F0C (3852) │ │ │ │ -C3206 Filename Length 0014 (20) │ │ │ │ -C3208 Extra Length 0018 (24) │ │ │ │ -C320A Comment Length 0000 (0) │ │ │ │ -C320C Disk Start 0000 (0) │ │ │ │ -C320E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3210 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3214 Local Header Offset 00042CBD (273597) │ │ │ │ -C3218 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3218: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C322C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C322E Length 0005 (5) │ │ │ │ -C3230 Flags 01 (1) 'Modification' │ │ │ │ -C3231 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3235 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3237 Length 000B (11) │ │ │ │ -C3239 Version 01 (1) │ │ │ │ -C323A UID Size 04 (4) │ │ │ │ -C323B UID 00000000 (0) │ │ │ │ -C323F GID Size 04 (4) │ │ │ │ -C3240 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3244 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -C3248 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3249 Created OS 03 (3) 'Unix' │ │ │ │ -C324A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C324B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C324C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C324E Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3250 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3254 CRC 3F5B0F93 (1062932371) │ │ │ │ -C3258 Compressed Size 00001260 (4704) │ │ │ │ -C325C Uncompressed Size 0000346B (13419) │ │ │ │ -C3260 Filename Length 0014 (20) │ │ │ │ -C3262 Extra Length 0018 (24) │ │ │ │ -C3264 Comment Length 0000 (0) │ │ │ │ -C3266 Disk Start 0000 (0) │ │ │ │ -C3268 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C326A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C326E Local Header Offset 00043108 (274696) │ │ │ │ -C3272 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3272: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3286 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3288 Length 0005 (5) │ │ │ │ -C328A Flags 01 (1) 'Modification' │ │ │ │ -C328B Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C328F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3291 Length 000B (11) │ │ │ │ -C3293 Version 01 (1) │ │ │ │ -C3294 UID Size 04 (4) │ │ │ │ -C3295 UID 00000000 (0) │ │ │ │ -C3299 GID Size 04 (4) │ │ │ │ -C329A GID 00000000 (0) │ │ │ │ - │ │ │ │ -C329E CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -C32A2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C32A3 Created OS 03 (3) 'Unix' │ │ │ │ -C32A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C32A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C32A6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C32A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C32AA Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C32AE CRC 41B13910 (1102133520) │ │ │ │ -C32B2 Compressed Size 00000ACC (2764) │ │ │ │ -C32B6 Uncompressed Size 000022FF (8959) │ │ │ │ -C32BA Filename Length 001B (27) │ │ │ │ -C32BC Extra Length 0018 (24) │ │ │ │ -C32BE Comment Length 0000 (0) │ │ │ │ -C32C0 Disk Start 0000 (0) │ │ │ │ -C32C2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C32C4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C32C8 Local Header Offset 000443B6 (279478) │ │ │ │ -C32CC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC32CC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C32E7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C32E9 Length 0005 (5) │ │ │ │ -C32EB Flags 01 (1) 'Modification' │ │ │ │ -C32EC Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C32F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C32F2 Length 000B (11) │ │ │ │ -C32F4 Version 01 (1) │ │ │ │ -C32F5 UID Size 04 (4) │ │ │ │ -C32F6 UID 00000000 (0) │ │ │ │ -C32FA GID Size 04 (4) │ │ │ │ -C32FB GID 00000000 (0) │ │ │ │ - │ │ │ │ -C32FF CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -C3303 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3304 Created OS 03 (3) 'Unix' │ │ │ │ -C3305 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3306 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3307 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3309 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C330B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C330F CRC DB2E23B4 (3677234100) │ │ │ │ -C3313 Compressed Size 00000C51 (3153) │ │ │ │ -C3317 Uncompressed Size 00002742 (10050) │ │ │ │ -C331B Filename Length 0013 (19) │ │ │ │ -C331D Extra Length 0018 (24) │ │ │ │ -C331F Comment Length 0000 (0) │ │ │ │ -C3321 Disk Start 0000 (0) │ │ │ │ -C3323 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3325 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3329 Local Header Offset 00044ED7 (282327) │ │ │ │ -C332D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC332D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3340 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3342 Length 0005 (5) │ │ │ │ -C3344 Flags 01 (1) 'Modification' │ │ │ │ -C3345 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3349 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C334B Length 000B (11) │ │ │ │ -C334D Version 01 (1) │ │ │ │ -C334E UID Size 04 (4) │ │ │ │ -C334F UID 00000000 (0) │ │ │ │ -C3353 GID Size 04 (4) │ │ │ │ -C3354 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3358 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -C335C Created Zip Spec 3D (61) '6.1' │ │ │ │ -C335D Created OS 03 (3) 'Unix' │ │ │ │ -C335E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C335F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3360 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3362 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3364 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3368 CRC 0705BEAC (117817004) │ │ │ │ -C336C Compressed Size 00000C95 (3221) │ │ │ │ -C3370 Uncompressed Size 00003D11 (15633) │ │ │ │ -C3374 Filename Length 0014 (20) │ │ │ │ -C3376 Extra Length 0018 (24) │ │ │ │ -C3378 Comment Length 0000 (0) │ │ │ │ -C337A Disk Start 0000 (0) │ │ │ │ -C337C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C337E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3382 Local Header Offset 00045B75 (285557) │ │ │ │ -C3386 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3386: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C339A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C339C Length 0005 (5) │ │ │ │ -C339E Flags 01 (1) 'Modification' │ │ │ │ -C339F Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C33A3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C33A5 Length 000B (11) │ │ │ │ -C33A7 Version 01 (1) │ │ │ │ -C33A8 UID Size 04 (4) │ │ │ │ -C33A9 UID 00000000 (0) │ │ │ │ -C33AD GID Size 04 (4) │ │ │ │ -C33AE GID 00000000 (0) │ │ │ │ - │ │ │ │ -C33B2 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -C33B6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C33B7 Created OS 03 (3) 'Unix' │ │ │ │ -C33B8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C33B9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C33BA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C33BC Compression Method 0008 (8) 'Deflated' │ │ │ │ -C33BE Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C33C2 CRC 356E7AD6 (896432854) │ │ │ │ -C33C6 Compressed Size 00000F43 (3907) │ │ │ │ -C33CA Uncompressed Size 00003744 (14148) │ │ │ │ -C33CE Filename Length 000F (15) │ │ │ │ -C33D0 Extra Length 0018 (24) │ │ │ │ -C33D2 Comment Length 0000 (0) │ │ │ │ -C33D4 Disk Start 0000 (0) │ │ │ │ -C33D6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C33D8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C33DC Local Header Offset 00046858 (288856) │ │ │ │ -C33E0 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC33E0: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C33EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C33F1 Length 0005 (5) │ │ │ │ -C33F3 Flags 01 (1) 'Modification' │ │ │ │ -C33F4 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C33F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C33FA Length 000B (11) │ │ │ │ -C33FC Version 01 (1) │ │ │ │ -C33FD UID Size 04 (4) │ │ │ │ -C33FE UID 00000000 (0) │ │ │ │ -C3402 GID Size 04 (4) │ │ │ │ -C3403 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3407 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -C340B Created Zip Spec 3D (61) '6.1' │ │ │ │ -C340C Created OS 03 (3) 'Unix' │ │ │ │ -C340D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C340E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C340F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3411 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3413 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3417 CRC AD08D6E7 (2903037671) │ │ │ │ -C341B Compressed Size 000006CE (1742) │ │ │ │ -C341F Uncompressed Size 00001AC4 (6852) │ │ │ │ -C3423 Filename Length 000F (15) │ │ │ │ -C3425 Extra Length 0018 (24) │ │ │ │ -C3427 Comment Length 0000 (0) │ │ │ │ -C3429 Disk Start 0000 (0) │ │ │ │ -C342B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C342D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3431 Local Header Offset 000477E4 (292836) │ │ │ │ -C3435 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3435: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3444 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3446 Length 0005 (5) │ │ │ │ -C3448 Flags 01 (1) 'Modification' │ │ │ │ -C3449 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C344D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C344F Length 000B (11) │ │ │ │ -C3451 Version 01 (1) │ │ │ │ -C3452 UID Size 04 (4) │ │ │ │ -C3453 UID 00000000 (0) │ │ │ │ -C3457 GID Size 04 (4) │ │ │ │ -C3458 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C345C CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -C3460 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3461 Created OS 03 (3) 'Unix' │ │ │ │ -C3462 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3463 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3464 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3466 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3468 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C346C CRC 27AAA5BB (665494971) │ │ │ │ -C3470 Compressed Size 00001A4F (6735) │ │ │ │ -C3474 Uncompressed Size 0000650E (25870) │ │ │ │ -C3478 Filename Length 0013 (19) │ │ │ │ -C347A Extra Length 0018 (24) │ │ │ │ -C347C Comment Length 0000 (0) │ │ │ │ -C347E Disk Start 0000 (0) │ │ │ │ -C3480 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3482 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3486 Local Header Offset 00047EFB (294651) │ │ │ │ -C348A Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC348A: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C349D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C349F Length 0005 (5) │ │ │ │ -C34A1 Flags 01 (1) 'Modification' │ │ │ │ -C34A2 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C34A6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C34A8 Length 000B (11) │ │ │ │ -C34AA Version 01 (1) │ │ │ │ -C34AB UID Size 04 (4) │ │ │ │ -C34AC UID 00000000 (0) │ │ │ │ -C34B0 GID Size 04 (4) │ │ │ │ -C34B1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C34B5 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -C34B9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C34BA Created OS 03 (3) 'Unix' │ │ │ │ -C34BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C34BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C34BD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C34BF Compression Method 0008 (8) 'Deflated' │ │ │ │ -C34C1 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C34C5 CRC 90967BFA (2425781242) │ │ │ │ -C34C9 Compressed Size 000009A6 (2470) │ │ │ │ -C34CD Uncompressed Size 00001B6A (7018) │ │ │ │ -C34D1 Filename Length 0010 (16) │ │ │ │ -C34D3 Extra Length 0018 (24) │ │ │ │ -C34D5 Comment Length 0000 (0) │ │ │ │ -C34D7 Disk Start 0000 (0) │ │ │ │ -C34D9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C34DB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C34DF Local Header Offset 00049997 (301463) │ │ │ │ -C34E3 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC34E3: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C34F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C34F5 Length 0005 (5) │ │ │ │ -C34F7 Flags 01 (1) 'Modification' │ │ │ │ -C34F8 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C34FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C34FE Length 000B (11) │ │ │ │ -C3500 Version 01 (1) │ │ │ │ -C3501 UID Size 04 (4) │ │ │ │ -C3502 UID 00000000 (0) │ │ │ │ -C3506 GID Size 04 (4) │ │ │ │ -C3507 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C350B CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -C350F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3510 Created OS 03 (3) 'Unix' │ │ │ │ -C3511 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3512 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3513 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3515 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3517 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C351B CRC D37ED789 (3548305289) │ │ │ │ -C351F Compressed Size 000006B6 (1718) │ │ │ │ -C3523 Uncompressed Size 00001565 (5477) │ │ │ │ -C3527 Filename Length 0012 (18) │ │ │ │ -C3529 Extra Length 0018 (24) │ │ │ │ -C352B Comment Length 0000 (0) │ │ │ │ -C352D Disk Start 0000 (0) │ │ │ │ -C352F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3531 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3535 Local Header Offset 0004A387 (304007) │ │ │ │ -C3539 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3539: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C354B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C354D Length 0005 (5) │ │ │ │ -C354F Flags 01 (1) 'Modification' │ │ │ │ -C3550 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3554 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3556 Length 000B (11) │ │ │ │ -C3558 Version 01 (1) │ │ │ │ -C3559 UID Size 04 (4) │ │ │ │ -C355A UID 00000000 (0) │ │ │ │ -C355E GID Size 04 (4) │ │ │ │ -C355F GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3563 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -C3567 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3568 Created OS 03 (3) 'Unix' │ │ │ │ -C3569 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C356A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C356B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C356D Compression Method 0008 (8) 'Deflated' │ │ │ │ -C356F Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3573 CRC 92D091A2 (2463142306) │ │ │ │ -C3577 Compressed Size 00002D59 (11609) │ │ │ │ -C357B Uncompressed Size 0000D083 (53379) │ │ │ │ -C357F Filename Length 0010 (16) │ │ │ │ -C3581 Extra Length 0018 (24) │ │ │ │ -C3583 Comment Length 0000 (0) │ │ │ │ -C3585 Disk Start 0000 (0) │ │ │ │ -C3587 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3589 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C358D Local Header Offset 0004AA89 (305801) │ │ │ │ -C3591 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3591: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C35A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C35A3 Length 0005 (5) │ │ │ │ -C35A5 Flags 01 (1) 'Modification' │ │ │ │ -C35A6 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C35AA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C35AC Length 000B (11) │ │ │ │ -C35AE Version 01 (1) │ │ │ │ -C35AF UID Size 04 (4) │ │ │ │ -C35B0 UID 00000000 (0) │ │ │ │ -C35B4 GID Size 04 (4) │ │ │ │ -C35B5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C35B9 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -C35BD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C35BE Created OS 03 (3) 'Unix' │ │ │ │ -C35BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C35C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C35C1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C35C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C35C5 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C35C9 CRC 608FA1E3 (1620025827) │ │ │ │ -C35CD Compressed Size 00001E84 (7812) │ │ │ │ -C35D1 Uncompressed Size 00009AAA (39594) │ │ │ │ -C35D5 Filename Length 0012 (18) │ │ │ │ -C35D7 Extra Length 0018 (24) │ │ │ │ -C35D9 Comment Length 0000 (0) │ │ │ │ -C35DB Disk Start 0000 (0) │ │ │ │ -C35DD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C35DF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C35E3 Local Header Offset 0004D82C (317484) │ │ │ │ -C35E7 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC35E7: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C35F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C35FB Length 0005 (5) │ │ │ │ -C35FD Flags 01 (1) 'Modification' │ │ │ │ -C35FE Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3602 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3604 Length 000B (11) │ │ │ │ -C3606 Version 01 (1) │ │ │ │ -C3607 UID Size 04 (4) │ │ │ │ -C3608 UID 00000000 (0) │ │ │ │ -C360C GID Size 04 (4) │ │ │ │ -C360D GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3611 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -C3615 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3616 Created OS 03 (3) 'Unix' │ │ │ │ -C3617 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3618 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3619 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C361B Compression Method 0008 (8) 'Deflated' │ │ │ │ -C361D Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3621 CRC 69109545 (1762694469) │ │ │ │ -C3625 Compressed Size 00001471 (5233) │ │ │ │ -C3629 Uncompressed Size 00007AD0 (31440) │ │ │ │ -C362D Filename Length 0018 (24) │ │ │ │ -C362F Extra Length 0018 (24) │ │ │ │ -C3631 Comment Length 0000 (0) │ │ │ │ -C3633 Disk Start 0000 (0) │ │ │ │ -C3635 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3637 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C363B Local Header Offset 0004F6FC (325372) │ │ │ │ -C363F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC363F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3657 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3659 Length 0005 (5) │ │ │ │ -C365B Flags 01 (1) 'Modification' │ │ │ │ -C365C Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3660 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3662 Length 000B (11) │ │ │ │ -C3664 Version 01 (1) │ │ │ │ -C3665 UID Size 04 (4) │ │ │ │ -C3666 UID 00000000 (0) │ │ │ │ -C366A GID Size 04 (4) │ │ │ │ -C366B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C366F CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -C3673 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3674 Created OS 03 (3) 'Unix' │ │ │ │ -C3675 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3676 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3677 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3679 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C367B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C367F CRC 274FF23B (659550779) │ │ │ │ -C3683 Compressed Size 000021DB (8667) │ │ │ │ -C3687 Uncompressed Size 0000D21D (53789) │ │ │ │ -C368B Filename Length 001F (31) │ │ │ │ -C368D Extra Length 0018 (24) │ │ │ │ -C368F Comment Length 0000 (0) │ │ │ │ -C3691 Disk Start 0000 (0) │ │ │ │ -C3693 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3695 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3699 Local Header Offset 00050BBF (330687) │ │ │ │ -C369D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC369D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C36BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C36BE Length 0005 (5) │ │ │ │ -C36C0 Flags 01 (1) 'Modification' │ │ │ │ -C36C1 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C36C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C36C7 Length 000B (11) │ │ │ │ -C36C9 Version 01 (1) │ │ │ │ -C36CA UID Size 04 (4) │ │ │ │ -C36CB UID 00000000 (0) │ │ │ │ -C36CF GID Size 04 (4) │ │ │ │ -C36D0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C36D4 CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -C36D8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C36D9 Created OS 03 (3) 'Unix' │ │ │ │ -C36DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C36DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C36DC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C36DE Compression Method 0008 (8) 'Deflated' │ │ │ │ -C36E0 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C36E4 CRC 585AC976 (1482344822) │ │ │ │ -C36E8 Compressed Size 000003F7 (1015) │ │ │ │ -C36EC Uncompressed Size 000008A3 (2211) │ │ │ │ -C36F0 Filename Length 001E (30) │ │ │ │ -C36F2 Extra Length 0018 (24) │ │ │ │ -C36F4 Comment Length 0000 (0) │ │ │ │ -C36F6 Disk Start 0000 (0) │ │ │ │ -C36F8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C36FA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C36FE Local Header Offset 00052DF3 (339443) │ │ │ │ -C3702 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3702: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3720 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3722 Length 0005 (5) │ │ │ │ -C3724 Flags 01 (1) 'Modification' │ │ │ │ -C3725 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3729 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C372B Length 000B (11) │ │ │ │ -C372D Version 01 (1) │ │ │ │ -C372E UID Size 04 (4) │ │ │ │ -C372F UID 00000000 (0) │ │ │ │ -C3733 GID Size 04 (4) │ │ │ │ -C3734 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3738 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -C373C Created Zip Spec 3D (61) '6.1' │ │ │ │ -C373D Created OS 03 (3) 'Unix' │ │ │ │ -C373E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C373F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3740 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3742 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3744 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3748 CRC 16308EDA (372281050) │ │ │ │ -C374C Compressed Size 00004361 (17249) │ │ │ │ -C3750 Uncompressed Size 0000E06F (57455) │ │ │ │ -C3754 Filename Length 0013 (19) │ │ │ │ -C3756 Extra Length 0018 (24) │ │ │ │ -C3758 Comment Length 0000 (0) │ │ │ │ -C375A Disk Start 0000 (0) │ │ │ │ -C375C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C375E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3762 Local Header Offset 00053242 (340546) │ │ │ │ -C3766 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3766: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3779 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C377B Length 0005 (5) │ │ │ │ -C377D Flags 01 (1) 'Modification' │ │ │ │ -C377E Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3782 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3784 Length 000B (11) │ │ │ │ -C3786 Version 01 (1) │ │ │ │ -C3787 UID Size 04 (4) │ │ │ │ -C3788 UID 00000000 (0) │ │ │ │ -C378C GID Size 04 (4) │ │ │ │ -C378D GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3791 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -C3795 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3796 Created OS 03 (3) 'Unix' │ │ │ │ -C3797 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3798 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3799 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C379B Compression Method 0008 (8) 'Deflated' │ │ │ │ -C379D Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C37A1 CRC 741EF03C (1948184636) │ │ │ │ -C37A5 Compressed Size 000026C3 (9923) │ │ │ │ -C37A9 Uncompressed Size 00006E45 (28229) │ │ │ │ -C37AD Filename Length 0019 (25) │ │ │ │ -C37AF Extra Length 0018 (24) │ │ │ │ -C37B1 Comment Length 0000 (0) │ │ │ │ -C37B3 Disk Start 0000 (0) │ │ │ │ -C37B5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C37B7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C37BB Local Header Offset 000575F0 (357872) │ │ │ │ -C37BF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC37BF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C37D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C37DA Length 0005 (5) │ │ │ │ -C37DC Flags 01 (1) 'Modification' │ │ │ │ -C37DD Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C37E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C37E3 Length 000B (11) │ │ │ │ -C37E5 Version 01 (1) │ │ │ │ -C37E6 UID Size 04 (4) │ │ │ │ -C37E7 UID 00000000 (0) │ │ │ │ -C37EB GID Size 04 (4) │ │ │ │ -C37EC GID 00000000 (0) │ │ │ │ - │ │ │ │ -C37F0 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -C37F4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C37F5 Created OS 03 (3) 'Unix' │ │ │ │ -C37F6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C37F7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C37F8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C37FA Compression Method 0008 (8) 'Deflated' │ │ │ │ -C37FC Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3800 CRC F8618863 (4167141475) │ │ │ │ -C3804 Compressed Size 00002738 (10040) │ │ │ │ -C3808 Uncompressed Size 00008B83 (35715) │ │ │ │ -C380C Filename Length 0019 (25) │ │ │ │ -C380E Extra Length 0018 (24) │ │ │ │ -C3810 Comment Length 0000 (0) │ │ │ │ -C3812 Disk Start 0000 (0) │ │ │ │ -C3814 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3816 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C381A Local Header Offset 00059D06 (367878) │ │ │ │ -C381E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC381E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3837 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3839 Length 0005 (5) │ │ │ │ -C383B Flags 01 (1) 'Modification' │ │ │ │ -C383C Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3840 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3842 Length 000B (11) │ │ │ │ -C3844 Version 01 (1) │ │ │ │ -C3845 UID Size 04 (4) │ │ │ │ -C3846 UID 00000000 (0) │ │ │ │ -C384A GID Size 04 (4) │ │ │ │ -C384B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C384F CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -C3853 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3854 Created OS 03 (3) 'Unix' │ │ │ │ -C3855 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3856 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3857 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3859 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C385B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C385F CRC 50018C09 (1342278665) │ │ │ │ -C3863 Compressed Size 00000ECF (3791) │ │ │ │ -C3867 Uncompressed Size 000053BF (21439) │ │ │ │ -C386B Filename Length 0021 (33) │ │ │ │ -C386D Extra Length 0018 (24) │ │ │ │ -C386F Comment Length 0000 (0) │ │ │ │ -C3871 Disk Start 0000 (0) │ │ │ │ -C3873 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3875 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3879 Local Header Offset 0005C491 (378001) │ │ │ │ -C387D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC387D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C389E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C38A0 Length 0005 (5) │ │ │ │ -C38A2 Flags 01 (1) 'Modification' │ │ │ │ -C38A3 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C38A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C38A9 Length 000B (11) │ │ │ │ -C38AB Version 01 (1) │ │ │ │ -C38AC UID Size 04 (4) │ │ │ │ -C38AD UID 00000000 (0) │ │ │ │ -C38B1 GID Size 04 (4) │ │ │ │ -C38B2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C38B6 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -C38BA Created Zip Spec 3D (61) '6.1' │ │ │ │ -C38BB Created OS 03 (3) 'Unix' │ │ │ │ -C38BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C38BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C38BE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C38C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C38C2 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C38C6 CRC D1EC3113 (3521917203) │ │ │ │ -C38CA Compressed Size 00000535 (1333) │ │ │ │ -C38CE Uncompressed Size 00000C96 (3222) │ │ │ │ -C38D2 Filename Length 0017 (23) │ │ │ │ -C38D4 Extra Length 0018 (24) │ │ │ │ -C38D6 Comment Length 0000 (0) │ │ │ │ -C38D8 Disk Start 0000 (0) │ │ │ │ -C38DA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C38DC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C38E0 Local Header Offset 0005D3BB (381883) │ │ │ │ -C38E4 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC38E4: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C38FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C38FD Length 0005 (5) │ │ │ │ -C38FF Flags 01 (1) 'Modification' │ │ │ │ -C3900 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3904 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3906 Length 000B (11) │ │ │ │ -C3908 Version 01 (1) │ │ │ │ -C3909 UID Size 04 (4) │ │ │ │ -C390A UID 00000000 (0) │ │ │ │ -C390E GID Size 04 (4) │ │ │ │ -C390F GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3913 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -C3917 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3918 Created OS 03 (3) 'Unix' │ │ │ │ -C3919 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C391A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C391B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C391D Compression Method 0008 (8) 'Deflated' │ │ │ │ -C391F Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3923 CRC 9ED6F2D9 (2664887001) │ │ │ │ -C3927 Compressed Size 00000467 (1127) │ │ │ │ -C392B Uncompressed Size 00000931 (2353) │ │ │ │ -C392F Filename Length 001B (27) │ │ │ │ -C3931 Extra Length 0018 (24) │ │ │ │ -C3933 Comment Length 0000 (0) │ │ │ │ -C3935 Disk Start 0000 (0) │ │ │ │ -C3937 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3939 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C393D Local Header Offset 0005D941 (383297) │ │ │ │ -C3941 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3941: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C395C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C395E Length 0005 (5) │ │ │ │ -C3960 Flags 01 (1) 'Modification' │ │ │ │ -C3961 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3965 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3967 Length 000B (11) │ │ │ │ -C3969 Version 01 (1) │ │ │ │ -C396A UID Size 04 (4) │ │ │ │ -C396B UID 00000000 (0) │ │ │ │ -C396F GID Size 04 (4) │ │ │ │ -C3970 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3974 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -C3978 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3979 Created OS 03 (3) 'Unix' │ │ │ │ -C397A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C397B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C397C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C397E Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3980 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3984 CRC BA8B93BA (3129709498) │ │ │ │ -C3988 Compressed Size 000016F5 (5877) │ │ │ │ -C398C Uncompressed Size 00007A86 (31366) │ │ │ │ -C3990 Filename Length 001F (31) │ │ │ │ -C3992 Extra Length 0018 (24) │ │ │ │ -C3994 Comment Length 0000 (0) │ │ │ │ -C3996 Disk Start 0000 (0) │ │ │ │ -C3998 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C399A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C399E Local Header Offset 0005DDFD (384509) │ │ │ │ -C39A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC39A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C39C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C39C3 Length 0005 (5) │ │ │ │ -C39C5 Flags 01 (1) 'Modification' │ │ │ │ -C39C6 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C39CA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C39CC Length 000B (11) │ │ │ │ -C39CE Version 01 (1) │ │ │ │ -C39CF UID Size 04 (4) │ │ │ │ -C39D0 UID 00000000 (0) │ │ │ │ -C39D4 GID Size 04 (4) │ │ │ │ -C39D5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C39D9 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -C39DD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C39DE Created OS 03 (3) 'Unix' │ │ │ │ -C39DF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C39E0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C39E1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C39E3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C39E5 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C39E9 CRC 7113BB88 (1897118600) │ │ │ │ -C39ED Compressed Size 00004171 (16753) │ │ │ │ -C39F1 Uncompressed Size 0001D163 (119139) │ │ │ │ -C39F5 Filename Length 0010 (16) │ │ │ │ -C39F7 Extra Length 0018 (24) │ │ │ │ -C39F9 Comment Length 0000 (0) │ │ │ │ -C39FB Disk Start 0000 (0) │ │ │ │ -C39FD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C39FF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3A03 Local Header Offset 0005F54B (390475) │ │ │ │ -C3A07 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3A07: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3A17 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3A19 Length 0005 (5) │ │ │ │ -C3A1B Flags 01 (1) 'Modification' │ │ │ │ -C3A1C Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3A20 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3A22 Length 000B (11) │ │ │ │ -C3A24 Version 01 (1) │ │ │ │ -C3A25 UID Size 04 (4) │ │ │ │ -C3A26 UID 00000000 (0) │ │ │ │ -C3A2A GID Size 04 (4) │ │ │ │ -C3A2B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3A2F CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -C3A33 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3A34 Created OS 03 (3) 'Unix' │ │ │ │ -C3A35 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3A36 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3A37 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3A39 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3A3B Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3A3F CRC C55EF92D (3311335725) │ │ │ │ -C3A43 Compressed Size 00000AE8 (2792) │ │ │ │ -C3A47 Uncompressed Size 000021E8 (8680) │ │ │ │ -C3A4B Filename Length 0014 (20) │ │ │ │ -C3A4D Extra Length 0018 (24) │ │ │ │ -C3A4F Comment Length 0000 (0) │ │ │ │ -C3A51 Disk Start 0000 (0) │ │ │ │ -C3A53 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3A55 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3A59 Local Header Offset 00063706 (407302) │ │ │ │ -C3A5D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3A5D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3A71 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3A73 Length 0005 (5) │ │ │ │ -C3A75 Flags 01 (1) 'Modification' │ │ │ │ -C3A76 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3A7A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3A7C Length 000B (11) │ │ │ │ -C3A7E Version 01 (1) │ │ │ │ -C3A7F UID Size 04 (4) │ │ │ │ -C3A80 UID 00000000 (0) │ │ │ │ -C3A84 GID Size 04 (4) │ │ │ │ -C3A85 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3A89 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -C3A8D Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3A8E Created OS 03 (3) 'Unix' │ │ │ │ -C3A8F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3A90 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3A91 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3A93 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3A95 Modification Time 5C8FA0E3 (1552916707) 'Wed Apr 15 20:07:06 2026' │ │ │ │ -C3A99 CRC B3826829 (3011668009) │ │ │ │ -C3A9D Compressed Size 0000B524 (46372) │ │ │ │ -C3AA1 Uncompressed Size 00041755 (268117) │ │ │ │ -C3AA5 Filename Length 0017 (23) │ │ │ │ -C3AA7 Extra Length 0018 (24) │ │ │ │ -C3AA9 Comment Length 0000 (0) │ │ │ │ -C3AAB Disk Start 0000 (0) │ │ │ │ -C3AAD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3AAF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3AB3 Local Header Offset 0006423C (410172) │ │ │ │ -C3AB7 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3AB7: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3ACE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3AD0 Length 0005 (5) │ │ │ │ -C3AD2 Flags 01 (1) 'Modification' │ │ │ │ -C3AD3 Modification Time 69DFEFEB (1776283627) 'Wed Apr 15 20:07:07 2026' │ │ │ │ -C3AD7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3AD9 Length 000B (11) │ │ │ │ -C3ADB Version 01 (1) │ │ │ │ -C3ADC UID Size 04 (4) │ │ │ │ -C3ADD UID 00000000 (0) │ │ │ │ -C3AE1 GID Size 04 (4) │ │ │ │ -C3AE2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3AE6 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -C3AEA Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3AEB Created OS 03 (3) 'Unix' │ │ │ │ -C3AEC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3AED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3AEE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3AF0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3AF2 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3AF6 CRC A49E0590 (2761819536) │ │ │ │ -C3AFA Compressed Size 000003FE (1022) │ │ │ │ -C3AFE Uncompressed Size 0000093D (2365) │ │ │ │ -C3B02 Filename Length 0013 (19) │ │ │ │ -C3B04 Extra Length 0018 (24) │ │ │ │ -C3B06 Comment Length 0000 (0) │ │ │ │ -C3B08 Disk Start 0000 (0) │ │ │ │ -C3B0A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3B0C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3B10 Local Header Offset 0006F7B1 (456625) │ │ │ │ -C3B14 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3B14: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3B27 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3B29 Length 0005 (5) │ │ │ │ -C3B2B Flags 01 (1) 'Modification' │ │ │ │ -C3B2C Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3B30 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3B32 Length 000B (11) │ │ │ │ -C3B34 Version 01 (1) │ │ │ │ -C3B35 UID Size 04 (4) │ │ │ │ -C3B36 UID 00000000 (0) │ │ │ │ -C3B3A GID Size 04 (4) │ │ │ │ -C3B3B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3B3F CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -C3B43 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3B44 Created OS 03 (3) 'Unix' │ │ │ │ -C3B45 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3B46 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3B47 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3B49 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3B4B Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3B4F CRC 1FD7FA48 (534248008) │ │ │ │ -C3B53 Compressed Size 000014D8 (5336) │ │ │ │ -C3B57 Uncompressed Size 00006892 (26770) │ │ │ │ -C3B5B Filename Length 0012 (18) │ │ │ │ -C3B5D Extra Length 0018 (24) │ │ │ │ -C3B5F Comment Length 0000 (0) │ │ │ │ -C3B61 Disk Start 0000 (0) │ │ │ │ -C3B63 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3B65 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3B69 Local Header Offset 0006FBFC (457724) │ │ │ │ -C3B6D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3B6D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3B7F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3B81 Length 0005 (5) │ │ │ │ -C3B83 Flags 01 (1) 'Modification' │ │ │ │ -C3B84 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3B88 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3B8A Length 000B (11) │ │ │ │ -C3B8C Version 01 (1) │ │ │ │ -C3B8D UID Size 04 (4) │ │ │ │ -C3B8E UID 00000000 (0) │ │ │ │ -C3B92 GID Size 04 (4) │ │ │ │ -C3B93 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3B97 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -C3B9B Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3B9C Created OS 03 (3) 'Unix' │ │ │ │ -C3B9D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3B9E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3B9F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3BA1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3BA3 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3BA7 CRC 7167272B (1902585643) │ │ │ │ -C3BAB Compressed Size 00001205 (4613) │ │ │ │ -C3BAF Uncompressed Size 0000414F (16719) │ │ │ │ -C3BB3 Filename Length 0012 (18) │ │ │ │ -C3BB5 Extra Length 0018 (24) │ │ │ │ -C3BB7 Comment Length 0000 (0) │ │ │ │ -C3BB9 Disk Start 0000 (0) │ │ │ │ -C3BBB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3BBD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3BC1 Local Header Offset 00071120 (463136) │ │ │ │ -C3BC5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3BC5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3BD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3BD9 Length 0005 (5) │ │ │ │ -C3BDB Flags 01 (1) 'Modification' │ │ │ │ -C3BDC Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3BE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3BE2 Length 000B (11) │ │ │ │ -C3BE4 Version 01 (1) │ │ │ │ -C3BE5 UID Size 04 (4) │ │ │ │ -C3BE6 UID 00000000 (0) │ │ │ │ -C3BEA GID Size 04 (4) │ │ │ │ -C3BEB GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3BEF CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -C3BF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3BF4 Created OS 03 (3) 'Unix' │ │ │ │ -C3BF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3BF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3BF7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3BF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3BFB Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3BFF CRC 5C8A4BDC (1552567260) │ │ │ │ -C3C03 Compressed Size 00000704 (1796) │ │ │ │ -C3C07 Uncompressed Size 000011A7 (4519) │ │ │ │ -C3C0B Filename Length 0019 (25) │ │ │ │ -C3C0D Extra Length 0018 (24) │ │ │ │ -C3C0F Comment Length 0000 (0) │ │ │ │ -C3C11 Disk Start 0000 (0) │ │ │ │ -C3C13 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3C15 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3C19 Local Header Offset 00072371 (467825) │ │ │ │ -C3C1D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3C1D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3C36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3C38 Length 0005 (5) │ │ │ │ -C3C3A Flags 01 (1) 'Modification' │ │ │ │ -C3C3B Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3C3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3C41 Length 000B (11) │ │ │ │ -C3C43 Version 01 (1) │ │ │ │ -C3C44 UID Size 04 (4) │ │ │ │ -C3C45 UID 00000000 (0) │ │ │ │ -C3C49 GID Size 04 (4) │ │ │ │ -C3C4A GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3C4E CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -C3C52 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3C53 Created OS 03 (3) 'Unix' │ │ │ │ -C3C54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3C55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3C56 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3C58 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3C5A Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3C5E CRC 337967C1 (863594433) │ │ │ │ -C3C62 Compressed Size 000018B8 (6328) │ │ │ │ -C3C66 Uncompressed Size 0000A678 (42616) │ │ │ │ -C3C6A Filename Length 0019 (25) │ │ │ │ -C3C6C Extra Length 0018 (24) │ │ │ │ -C3C6E Comment Length 0000 (0) │ │ │ │ -C3C70 Disk Start 0000 (0) │ │ │ │ -C3C72 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3C74 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3C78 Local Header Offset 00072AC8 (469704) │ │ │ │ -C3C7C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3C7C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3C95 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3C97 Length 0005 (5) │ │ │ │ -C3C99 Flags 01 (1) 'Modification' │ │ │ │ -C3C9A Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3C9E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3CA0 Length 000B (11) │ │ │ │ -C3CA2 Version 01 (1) │ │ │ │ -C3CA3 UID Size 04 (4) │ │ │ │ -C3CA4 UID 00000000 (0) │ │ │ │ -C3CA8 GID Size 04 (4) │ │ │ │ -C3CA9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3CAD CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -C3CB1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3CB2 Created OS 03 (3) 'Unix' │ │ │ │ -C3CB3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3CB4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3CB5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3CB7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3CB9 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3CBD CRC 4D1409E6 (1293158886) │ │ │ │ -C3CC1 Compressed Size 0000177C (6012) │ │ │ │ -C3CC5 Uncompressed Size 0000472C (18220) │ │ │ │ -C3CC9 Filename Length 0014 (20) │ │ │ │ -C3CCB Extra Length 0018 (24) │ │ │ │ -C3CCD Comment Length 0000 (0) │ │ │ │ -C3CCF Disk Start 0000 (0) │ │ │ │ -C3CD1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3CD3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3CD7 Local Header Offset 000743D3 (476115) │ │ │ │ -C3CDB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3CDB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3CEF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3CF1 Length 0005 (5) │ │ │ │ -C3CF3 Flags 01 (1) 'Modification' │ │ │ │ -C3CF4 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3CF8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3CFA Length 000B (11) │ │ │ │ -C3CFC Version 01 (1) │ │ │ │ -C3CFD UID Size 04 (4) │ │ │ │ -C3CFE UID 00000000 (0) │ │ │ │ -C3D02 GID Size 04 (4) │ │ │ │ -C3D03 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3D07 CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -C3D0B Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3D0C Created OS 03 (3) 'Unix' │ │ │ │ -C3D0D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3D0E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3D0F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3D11 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3D13 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3D17 CRC 2F723DFF (796016127) │ │ │ │ -C3D1B Compressed Size 00000409 (1033) │ │ │ │ -C3D1F Uncompressed Size 00000825 (2085) │ │ │ │ -C3D23 Filename Length 001C (28) │ │ │ │ -C3D25 Extra Length 0018 (24) │ │ │ │ -C3D27 Comment Length 0000 (0) │ │ │ │ -C3D29 Disk Start 0000 (0) │ │ │ │ -C3D2B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3D2D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3D31 Local Header Offset 00075B9D (482205) │ │ │ │ -C3D35 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3D35: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3D51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3D53 Length 0005 (5) │ │ │ │ -C3D55 Flags 01 (1) 'Modification' │ │ │ │ -C3D56 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3D5A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3D5C Length 000B (11) │ │ │ │ -C3D5E Version 01 (1) │ │ │ │ -C3D5F UID Size 04 (4) │ │ │ │ -C3D60 UID 00000000 (0) │ │ │ │ -C3D64 GID Size 04 (4) │ │ │ │ -C3D65 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3D69 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -C3D6D Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3D6E Created OS 03 (3) 'Unix' │ │ │ │ -C3D6F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3D70 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3D71 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3D73 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3D75 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3D79 CRC CCD906B0 (3436775088) │ │ │ │ -C3D7D Compressed Size 000024C6 (9414) │ │ │ │ -C3D81 Uncompressed Size 0000B65D (46685) │ │ │ │ -C3D85 Filename Length 001F (31) │ │ │ │ -C3D87 Extra Length 0018 (24) │ │ │ │ -C3D89 Comment Length 0000 (0) │ │ │ │ -C3D8B Disk Start 0000 (0) │ │ │ │ -C3D8D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3D8F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3D93 Local Header Offset 00075FFC (483324) │ │ │ │ -C3D97 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3D97: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3DB6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3DB8 Length 0005 (5) │ │ │ │ -C3DBA Flags 01 (1) 'Modification' │ │ │ │ -C3DBB Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3DBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3DC1 Length 000B (11) │ │ │ │ -C3DC3 Version 01 (1) │ │ │ │ -C3DC4 UID Size 04 (4) │ │ │ │ -C3DC5 UID 00000000 (0) │ │ │ │ -C3DC9 GID Size 04 (4) │ │ │ │ -C3DCA GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3DCE CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -C3DD2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3DD3 Created OS 03 (3) 'Unix' │ │ │ │ -C3DD4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3DD5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3DD6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3DD8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3DDA Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3DDE CRC E78CD1A8 (3884765608) │ │ │ │ -C3DE2 Compressed Size 00000E7C (3708) │ │ │ │ -C3DE6 Uncompressed Size 000052DA (21210) │ │ │ │ -C3DEA Filename Length 001F (31) │ │ │ │ -C3DEC Extra Length 0018 (24) │ │ │ │ -C3DEE Comment Length 0000 (0) │ │ │ │ -C3DF0 Disk Start 0000 (0) │ │ │ │ -C3DF2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3DF4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3DF8 Local Header Offset 0007851B (492827) │ │ │ │ -C3DFC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3DFC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3E1B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3E1D Length 0005 (5) │ │ │ │ -C3E1F Flags 01 (1) 'Modification' │ │ │ │ -C3E20 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3E24 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3E26 Length 000B (11) │ │ │ │ -C3E28 Version 01 (1) │ │ │ │ -C3E29 UID Size 04 (4) │ │ │ │ -C3E2A UID 00000000 (0) │ │ │ │ -C3E2E GID Size 04 (4) │ │ │ │ -C3E2F GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3E33 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -C3E37 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3E38 Created OS 03 (3) 'Unix' │ │ │ │ -C3E39 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3E3A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3E3B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3E3D Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3E3F Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3E43 CRC E196989B (3784743067) │ │ │ │ -C3E47 Compressed Size 00000A44 (2628) │ │ │ │ -C3E4B Uncompressed Size 0000247A (9338) │ │ │ │ -C3E4F Filename Length 0013 (19) │ │ │ │ -C3E51 Extra Length 0018 (24) │ │ │ │ -C3E53 Comment Length 0000 (0) │ │ │ │ -C3E55 Disk Start 0000 (0) │ │ │ │ -C3E57 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3E59 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3E5D Local Header Offset 000793F0 (496624) │ │ │ │ -C3E61 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3E61: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3E74 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3E76 Length 0005 (5) │ │ │ │ -C3E78 Flags 01 (1) 'Modification' │ │ │ │ -C3E79 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3E7D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3E7F Length 000B (11) │ │ │ │ -C3E81 Version 01 (1) │ │ │ │ -C3E82 UID Size 04 (4) │ │ │ │ -C3E83 UID 00000000 (0) │ │ │ │ -C3E87 GID Size 04 (4) │ │ │ │ -C3E88 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3E8C CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -C3E90 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3E91 Created OS 03 (3) 'Unix' │ │ │ │ -C3E92 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3E93 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3E94 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3E96 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3E98 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3E9C CRC 34AC5B41 (883710785) │ │ │ │ -C3EA0 Compressed Size 00002591 (9617) │ │ │ │ -C3EA4 Uncompressed Size 0000BAA4 (47780) │ │ │ │ -C3EA8 Filename Length 0019 (25) │ │ │ │ -C3EAA Extra Length 0018 (24) │ │ │ │ -C3EAC Comment Length 0000 (0) │ │ │ │ -C3EAE Disk Start 0000 (0) │ │ │ │ -C3EB0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3EB2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3EB6 Local Header Offset 00079E81 (499329) │ │ │ │ -C3EBA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3EBA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3ED3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3ED5 Length 0005 (5) │ │ │ │ -C3ED7 Flags 01 (1) 'Modification' │ │ │ │ -C3ED8 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3EDC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3EDE Length 000B (11) │ │ │ │ -C3EE0 Version 01 (1) │ │ │ │ -C3EE1 UID Size 04 (4) │ │ │ │ -C3EE2 UID 00000000 (0) │ │ │ │ -C3EE6 GID Size 04 (4) │ │ │ │ -C3EE7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3EEB CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -C3EEF Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3EF0 Created OS 03 (3) 'Unix' │ │ │ │ -C3EF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3EF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3EF3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3EF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3EF7 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3EFB CRC 97E60E7F (2548436607) │ │ │ │ -C3EFF Compressed Size 00000EFD (3837) │ │ │ │ -C3F03 Uncompressed Size 00003A2F (14895) │ │ │ │ -C3F07 Filename Length 0024 (36) │ │ │ │ -C3F09 Extra Length 0018 (24) │ │ │ │ -C3F0B Comment Length 0000 (0) │ │ │ │ -C3F0D Disk Start 0000 (0) │ │ │ │ -C3F0F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3F11 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3F15 Local Header Offset 0007C465 (509029) │ │ │ │ -C3F19 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3F19: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3F3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3F3F Length 0005 (5) │ │ │ │ -C3F41 Flags 01 (1) 'Modification' │ │ │ │ -C3F42 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3F46 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3F48 Length 000B (11) │ │ │ │ -C3F4A Version 01 (1) │ │ │ │ -C3F4B UID Size 04 (4) │ │ │ │ -C3F4C UID 00000000 (0) │ │ │ │ -C3F50 GID Size 04 (4) │ │ │ │ -C3F51 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3F55 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -C3F59 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3F5A Created OS 03 (3) 'Unix' │ │ │ │ -C3F5B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3F5C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3F5D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3F5F Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3F61 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3F65 CRC 247F076B (612304747) │ │ │ │ -C3F69 Compressed Size 00001AEA (6890) │ │ │ │ -C3F6D Uncompressed Size 00005F7F (24447) │ │ │ │ -C3F71 Filename Length 0017 (23) │ │ │ │ -C3F73 Extra Length 0018 (24) │ │ │ │ -C3F75 Comment Length 0000 (0) │ │ │ │ -C3F77 Disk Start 0000 (0) │ │ │ │ -C3F79 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3F7B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3F7F Local Header Offset 0007D3C0 (512960) │ │ │ │ -C3F83 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3F83: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C3F9A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C3F9C Length 0005 (5) │ │ │ │ -C3F9E Flags 01 (1) 'Modification' │ │ │ │ -C3F9F Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3FA3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C3FA5 Length 000B (11) │ │ │ │ -C3FA7 Version 01 (1) │ │ │ │ -C3FA8 UID Size 04 (4) │ │ │ │ -C3FA9 UID 00000000 (0) │ │ │ │ -C3FAD GID Size 04 (4) │ │ │ │ -C3FAE GID 00000000 (0) │ │ │ │ - │ │ │ │ -C3FB2 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -C3FB6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C3FB7 Created OS 03 (3) 'Unix' │ │ │ │ -C3FB8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C3FB9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C3FBA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C3FBC Compression Method 0008 (8) 'Deflated' │ │ │ │ -C3FBE Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C3FC2 CRC 11E32AF1 (300100337) │ │ │ │ -C3FC6 Compressed Size 00000ED3 (3795) │ │ │ │ -C3FCA Uncompressed Size 000038E2 (14562) │ │ │ │ -C3FCE Filename Length 0023 (35) │ │ │ │ -C3FD0 Extra Length 0018 (24) │ │ │ │ -C3FD2 Comment Length 0000 (0) │ │ │ │ -C3FD4 Disk Start 0000 (0) │ │ │ │ -C3FD6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C3FD8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C3FDC Local Header Offset 0007EEFB (519931) │ │ │ │ -C3FE0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC3FE0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4003 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4005 Length 0005 (5) │ │ │ │ -C4007 Flags 01 (1) 'Modification' │ │ │ │ -C4008 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C400C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C400E Length 000B (11) │ │ │ │ -C4010 Version 01 (1) │ │ │ │ -C4011 UID Size 04 (4) │ │ │ │ -C4012 UID 00000000 (0) │ │ │ │ -C4016 GID Size 04 (4) │ │ │ │ -C4017 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C401B CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -C401F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4020 Created OS 03 (3) 'Unix' │ │ │ │ -C4021 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4022 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4023 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4025 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4027 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C402B CRC 2DB7929F (767005343) │ │ │ │ -C402F Compressed Size 00000113 (275) │ │ │ │ -C4033 Uncompressed Size 000001F3 (499) │ │ │ │ -C4037 Filename Length 001B (27) │ │ │ │ -C4039 Extra Length 0018 (24) │ │ │ │ -C403B Comment Length 0000 (0) │ │ │ │ -C403D Disk Start 0000 (0) │ │ │ │ -C403F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4041 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4045 Local Header Offset 0007FE2B (523819) │ │ │ │ -C4049 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4049: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4064 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4066 Length 0005 (5) │ │ │ │ -C4068 Flags 01 (1) 'Modification' │ │ │ │ -C4069 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C406D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C406F Length 000B (11) │ │ │ │ -C4071 Version 01 (1) │ │ │ │ -C4072 UID Size 04 (4) │ │ │ │ -C4073 UID 00000000 (0) │ │ │ │ -C4077 GID Size 04 (4) │ │ │ │ -C4078 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C407C CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -C4080 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4081 Created OS 03 (3) 'Unix' │ │ │ │ -C4082 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4083 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4084 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4086 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4088 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C408C CRC 3E2ED2F5 (1043256053) │ │ │ │ -C4090 Compressed Size 0000188D (6285) │ │ │ │ -C4094 Uncompressed Size 00008FA8 (36776) │ │ │ │ -C4098 Filename Length 001D (29) │ │ │ │ -C409A Extra Length 0018 (24) │ │ │ │ -C409C Comment Length 0000 (0) │ │ │ │ -C409E Disk Start 0000 (0) │ │ │ │ -C40A0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C40A2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C40A6 Local Header Offset 0007FF93 (524179) │ │ │ │ -C40AA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC40AA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C40C7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C40C9 Length 0005 (5) │ │ │ │ -C40CB Flags 01 (1) 'Modification' │ │ │ │ -C40CC Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C40D0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C40D2 Length 000B (11) │ │ │ │ -C40D4 Version 01 (1) │ │ │ │ -C40D5 UID Size 04 (4) │ │ │ │ -C40D6 UID 00000000 (0) │ │ │ │ -C40DA GID Size 04 (4) │ │ │ │ -C40DB GID 00000000 (0) │ │ │ │ - │ │ │ │ -C40DF CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -C40E3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C40E4 Created OS 03 (3) 'Unix' │ │ │ │ -C40E5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C40E6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C40E7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C40E9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C40EB Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C40EF CRC 13F8A234 (335061556) │ │ │ │ -C40F3 Compressed Size 0000164C (5708) │ │ │ │ -C40F7 Uncompressed Size 00003A9B (15003) │ │ │ │ -C40FB Filename Length 0015 (21) │ │ │ │ -C40FD Extra Length 0018 (24) │ │ │ │ -C40FF Comment Length 0000 (0) │ │ │ │ -C4101 Disk Start 0000 (0) │ │ │ │ -C4103 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4105 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4109 Local Header Offset 00081877 (530551) │ │ │ │ -C410D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC410D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4122 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4124 Length 0005 (5) │ │ │ │ -C4126 Flags 01 (1) 'Modification' │ │ │ │ -C4127 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C412B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C412D Length 000B (11) │ │ │ │ -C412F Version 01 (1) │ │ │ │ -C4130 UID Size 04 (4) │ │ │ │ -C4131 UID 00000000 (0) │ │ │ │ -C4135 GID Size 04 (4) │ │ │ │ -C4136 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C413A CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -C413E Created Zip Spec 3D (61) '6.1' │ │ │ │ -C413F Created OS 03 (3) 'Unix' │ │ │ │ -C4140 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4141 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4142 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4144 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4146 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C414A CRC 945176DD (2488366813) │ │ │ │ -C414E Compressed Size 000040CE (16590) │ │ │ │ -C4152 Uncompressed Size 000133AC (78764) │ │ │ │ -C4156 Filename Length 0016 (22) │ │ │ │ -C4158 Extra Length 0018 (24) │ │ │ │ -C415A Comment Length 0000 (0) │ │ │ │ -C415C Disk Start 0000 (0) │ │ │ │ -C415E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4160 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4164 Local Header Offset 00082F12 (536338) │ │ │ │ -C4168 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4168: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C417E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4180 Length 0005 (5) │ │ │ │ -C4182 Flags 01 (1) 'Modification' │ │ │ │ -C4183 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4187 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4189 Length 000B (11) │ │ │ │ -C418B Version 01 (1) │ │ │ │ -C418C UID Size 04 (4) │ │ │ │ -C418D UID 00000000 (0) │ │ │ │ -C4191 GID Size 04 (4) │ │ │ │ -C4192 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4196 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -C419A Created Zip Spec 3D (61) '6.1' │ │ │ │ -C419B Created OS 03 (3) 'Unix' │ │ │ │ -C419C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C419D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C419E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C41A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C41A2 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C41A6 CRC FF953A42 (4287969858) │ │ │ │ -C41AA Compressed Size 00003EB3 (16051) │ │ │ │ -C41AE Uncompressed Size 0001C78B (116619) │ │ │ │ -C41B2 Filename Length 0019 (25) │ │ │ │ -C41B4 Extra Length 0018 (24) │ │ │ │ -C41B6 Comment Length 0000 (0) │ │ │ │ -C41B8 Disk Start 0000 (0) │ │ │ │ -C41BA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C41BC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C41C0 Local Header Offset 00087030 (553008) │ │ │ │ -C41C4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC41C4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C41DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C41DF Length 0005 (5) │ │ │ │ -C41E1 Flags 01 (1) 'Modification' │ │ │ │ -C41E2 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C41E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C41E8 Length 000B (11) │ │ │ │ -C41EA Version 01 (1) │ │ │ │ -C41EB UID Size 04 (4) │ │ │ │ -C41EC UID 00000000 (0) │ │ │ │ -C41F0 GID Size 04 (4) │ │ │ │ -C41F1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C41F5 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -C41F9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C41FA Created OS 03 (3) 'Unix' │ │ │ │ -C41FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C41FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C41FD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C41FF Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4201 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4205 CRC D20AC80F (3523921935) │ │ │ │ -C4209 Compressed Size 0000089D (2205) │ │ │ │ -C420D Uncompressed Size 000036CC (14028) │ │ │ │ -C4211 Filename Length 0011 (17) │ │ │ │ -C4213 Extra Length 0018 (24) │ │ │ │ -C4215 Comment Length 0000 (0) │ │ │ │ -C4217 Disk Start 0000 (0) │ │ │ │ -C4219 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C421B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C421F Local Header Offset 0008AF36 (569142) │ │ │ │ -C4223 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4223: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4234 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4236 Length 0005 (5) │ │ │ │ -C4238 Flags 01 (1) 'Modification' │ │ │ │ -C4239 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C423D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C423F Length 000B (11) │ │ │ │ -C4241 Version 01 (1) │ │ │ │ -C4242 UID Size 04 (4) │ │ │ │ -C4243 UID 00000000 (0) │ │ │ │ -C4247 GID Size 04 (4) │ │ │ │ -C4248 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C424C CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -C4250 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4251 Created OS 03 (3) 'Unix' │ │ │ │ -C4252 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4253 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4254 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4256 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4258 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C425C CRC ACBCD47B (2898056315) │ │ │ │ -C4260 Compressed Size 0000519E (20894) │ │ │ │ -C4264 Uncompressed Size 0001F99A (129434) │ │ │ │ -C4268 Filename Length 0015 (21) │ │ │ │ -C426A Extra Length 0018 (24) │ │ │ │ -C426C Comment Length 0000 (0) │ │ │ │ -C426E Disk Start 0000 (0) │ │ │ │ -C4270 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4272 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4276 Local Header Offset 0008B81E (571422) │ │ │ │ -C427A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC427A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C428F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4291 Length 0005 (5) │ │ │ │ -C4293 Flags 01 (1) 'Modification' │ │ │ │ -C4294 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4298 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C429A Length 000B (11) │ │ │ │ -C429C Version 01 (1) │ │ │ │ -C429D UID Size 04 (4) │ │ │ │ -C429E UID 00000000 (0) │ │ │ │ -C42A2 GID Size 04 (4) │ │ │ │ -C42A3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C42A7 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -C42AB Created Zip Spec 3D (61) '6.1' │ │ │ │ -C42AC Created OS 03 (3) 'Unix' │ │ │ │ -C42AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C42AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C42AF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C42B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C42B3 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C42B7 CRC 2CBD0730 (750585648) │ │ │ │ -C42BB Compressed Size 00001C41 (7233) │ │ │ │ -C42BF Uncompressed Size 00008AC8 (35528) │ │ │ │ -C42C3 Filename Length 0019 (25) │ │ │ │ -C42C5 Extra Length 0018 (24) │ │ │ │ -C42C7 Comment Length 0000 (0) │ │ │ │ -C42C9 Disk Start 0000 (0) │ │ │ │ -C42CB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C42CD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C42D1 Local Header Offset 00090A0B (592395) │ │ │ │ -C42D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC42D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C42EE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C42F0 Length 0005 (5) │ │ │ │ -C42F2 Flags 01 (1) 'Modification' │ │ │ │ -C42F3 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C42F7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C42F9 Length 000B (11) │ │ │ │ -C42FB Version 01 (1) │ │ │ │ -C42FC UID Size 04 (4) │ │ │ │ -C42FD UID 00000000 (0) │ │ │ │ -C4301 GID Size 04 (4) │ │ │ │ -C4302 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4306 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -C430A Created Zip Spec 3D (61) '6.1' │ │ │ │ -C430B Created OS 03 (3) 'Unix' │ │ │ │ -C430C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C430D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C430E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4310 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4312 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4316 CRC 896CE926 (2305616166) │ │ │ │ -C431A Compressed Size 00000D91 (3473) │ │ │ │ -C431E Uncompressed Size 00002EA4 (11940) │ │ │ │ -C4322 Filename Length 0018 (24) │ │ │ │ -C4324 Extra Length 0018 (24) │ │ │ │ -C4326 Comment Length 0000 (0) │ │ │ │ -C4328 Disk Start 0000 (0) │ │ │ │ -C432A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C432C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4330 Local Header Offset 0009269F (599711) │ │ │ │ -C4334 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4334: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C434C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C434E Length 0005 (5) │ │ │ │ -C4350 Flags 01 (1) 'Modification' │ │ │ │ -C4351 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4355 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4357 Length 000B (11) │ │ │ │ -C4359 Version 01 (1) │ │ │ │ -C435A UID Size 04 (4) │ │ │ │ -C435B UID 00000000 (0) │ │ │ │ -C435F GID Size 04 (4) │ │ │ │ -C4360 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4364 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -C4368 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4369 Created OS 03 (3) 'Unix' │ │ │ │ -C436A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C436B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C436C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C436E Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4370 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4374 CRC 535B63FB (1398498299) │ │ │ │ -C4378 Compressed Size 000001DF (479) │ │ │ │ -C437C Uncompressed Size 00000323 (803) │ │ │ │ -C4380 Filename Length 0011 (17) │ │ │ │ -C4382 Extra Length 0018 (24) │ │ │ │ -C4384 Comment Length 0000 (0) │ │ │ │ -C4386 Disk Start 0000 (0) │ │ │ │ -C4388 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C438A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C438E Local Header Offset 00093482 (603266) │ │ │ │ -C4392 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4392: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C43A3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C43A5 Length 0005 (5) │ │ │ │ -C43A7 Flags 01 (1) 'Modification' │ │ │ │ -C43A8 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C43AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C43AE Length 000B (11) │ │ │ │ -C43B0 Version 01 (1) │ │ │ │ -C43B1 UID Size 04 (4) │ │ │ │ -C43B2 UID 00000000 (0) │ │ │ │ -C43B6 GID Size 04 (4) │ │ │ │ -C43B7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C43BB CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -C43BF Created Zip Spec 3D (61) '6.1' │ │ │ │ -C43C0 Created OS 03 (3) 'Unix' │ │ │ │ -C43C1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C43C2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C43C3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C43C5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C43C7 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C43CB CRC 32CAD6D8 (852154072) │ │ │ │ -C43CF Compressed Size 000006BE (1726) │ │ │ │ -C43D3 Uncompressed Size 0000141F (5151) │ │ │ │ -C43D7 Filename Length 0019 (25) │ │ │ │ -C43D9 Extra Length 0018 (24) │ │ │ │ -C43DB Comment Length 0000 (0) │ │ │ │ -C43DD Disk Start 0000 (0) │ │ │ │ -C43DF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C43E1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C43E5 Local Header Offset 000936AC (603820) │ │ │ │ -C43E9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC43E9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4402 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4404 Length 0005 (5) │ │ │ │ -C4406 Flags 01 (1) 'Modification' │ │ │ │ -C4407 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C440B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C440D Length 000B (11) │ │ │ │ -C440F Version 01 (1) │ │ │ │ -C4410 UID Size 04 (4) │ │ │ │ -C4411 UID 00000000 (0) │ │ │ │ -C4415 GID Size 04 (4) │ │ │ │ -C4416 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C441A CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -C441E Created Zip Spec 3D (61) '6.1' │ │ │ │ -C441F Created OS 03 (3) 'Unix' │ │ │ │ -C4420 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4421 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4422 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4424 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4426 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C442A CRC FC6D6CD0 (4235029712) │ │ │ │ -C442E Compressed Size 00001B8D (7053) │ │ │ │ -C4432 Uncompressed Size 00009F5F (40799) │ │ │ │ -C4436 Filename Length 0018 (24) │ │ │ │ -C4438 Extra Length 0018 (24) │ │ │ │ -C443A Comment Length 0000 (0) │ │ │ │ -C443C Disk Start 0000 (0) │ │ │ │ -C443E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4440 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4444 Local Header Offset 00093DBD (605629) │ │ │ │ -C4448 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4448: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4460 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4462 Length 0005 (5) │ │ │ │ -C4464 Flags 01 (1) 'Modification' │ │ │ │ -C4465 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4469 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C446B Length 000B (11) │ │ │ │ -C446D Version 01 (1) │ │ │ │ -C446E UID Size 04 (4) │ │ │ │ -C446F UID 00000000 (0) │ │ │ │ -C4473 GID Size 04 (4) │ │ │ │ -C4474 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4478 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -C447C Created Zip Spec 3D (61) '6.1' │ │ │ │ -C447D Created OS 03 (3) 'Unix' │ │ │ │ -C447E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C447F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4480 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4482 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4484 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4488 CRC 11186130 (286810416) │ │ │ │ -C448C Compressed Size 000016FF (5887) │ │ │ │ -C4490 Uncompressed Size 00008B12 (35602) │ │ │ │ -C4494 Filename Length 0012 (18) │ │ │ │ -C4496 Extra Length 0018 (24) │ │ │ │ -C4498 Comment Length 0000 (0) │ │ │ │ -C449A Disk Start 0000 (0) │ │ │ │ -C449C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C449E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C44A2 Local Header Offset 0009599C (612764) │ │ │ │ -C44A6 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC44A6: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C44B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C44BA Length 0005 (5) │ │ │ │ -C44BC Flags 01 (1) 'Modification' │ │ │ │ -C44BD Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C44C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C44C3 Length 000B (11) │ │ │ │ -C44C5 Version 01 (1) │ │ │ │ -C44C6 UID Size 04 (4) │ │ │ │ -C44C7 UID 00000000 (0) │ │ │ │ -C44CB GID Size 04 (4) │ │ │ │ -C44CC GID 00000000 (0) │ │ │ │ - │ │ │ │ -C44D0 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -C44D4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C44D5 Created OS 03 (3) 'Unix' │ │ │ │ -C44D6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C44D7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C44D8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C44DA Compression Method 0008 (8) 'Deflated' │ │ │ │ -C44DC Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C44E0 CRC 0D2BB7BA (220968890) │ │ │ │ -C44E4 Compressed Size 00001E0B (7691) │ │ │ │ -C44E8 Uncompressed Size 00008823 (34851) │ │ │ │ -C44EC Filename Length 0016 (22) │ │ │ │ -C44EE Extra Length 0018 (24) │ │ │ │ -C44F0 Comment Length 0000 (0) │ │ │ │ -C44F2 Disk Start 0000 (0) │ │ │ │ -C44F4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C44F6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C44FA Local Header Offset 000970E7 (618727) │ │ │ │ -C44FE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC44FE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4514 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4516 Length 0005 (5) │ │ │ │ -C4518 Flags 01 (1) 'Modification' │ │ │ │ -C4519 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C451D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C451F Length 000B (11) │ │ │ │ -C4521 Version 01 (1) │ │ │ │ -C4522 UID Size 04 (4) │ │ │ │ -C4523 UID 00000000 (0) │ │ │ │ -C4527 GID Size 04 (4) │ │ │ │ -C4528 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C452C CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -C4530 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4531 Created OS 03 (3) 'Unix' │ │ │ │ -C4532 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4533 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4534 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4536 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4538 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C453C CRC AA93B274 (2861806196) │ │ │ │ -C4540 Compressed Size 000029A8 (10664) │ │ │ │ -C4544 Uncompressed Size 0000D04F (53327) │ │ │ │ -C4548 Filename Length 001A (26) │ │ │ │ -C454A Extra Length 0018 (24) │ │ │ │ -C454C Comment Length 0000 (0) │ │ │ │ -C454E Disk Start 0000 (0) │ │ │ │ -C4550 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4552 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4556 Local Header Offset 00098F42 (626498) │ │ │ │ -C455A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC455A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4574 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4576 Length 0005 (5) │ │ │ │ -C4578 Flags 01 (1) 'Modification' │ │ │ │ -C4579 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C457D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C457F Length 000B (11) │ │ │ │ -C4581 Version 01 (1) │ │ │ │ -C4582 UID Size 04 (4) │ │ │ │ -C4583 UID 00000000 (0) │ │ │ │ -C4587 GID Size 04 (4) │ │ │ │ -C4588 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C458C CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -C4590 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4591 Created OS 03 (3) 'Unix' │ │ │ │ -C4592 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4593 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4594 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4596 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4598 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C459C CRC C898661A (3365430810) │ │ │ │ -C45A0 Compressed Size 000009AB (2475) │ │ │ │ -C45A4 Uncompressed Size 00001DAC (7596) │ │ │ │ -C45A8 Filename Length 0018 (24) │ │ │ │ -C45AA Extra Length 0018 (24) │ │ │ │ -C45AC Comment Length 0000 (0) │ │ │ │ -C45AE Disk Start 0000 (0) │ │ │ │ -C45B0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C45B2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C45B6 Local Header Offset 0009B93E (637246) │ │ │ │ -C45BA Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC45BA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C45D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C45D4 Length 0005 (5) │ │ │ │ -C45D6 Flags 01 (1) 'Modification' │ │ │ │ -C45D7 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C45DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C45DD Length 000B (11) │ │ │ │ -C45DF Version 01 (1) │ │ │ │ -C45E0 UID Size 04 (4) │ │ │ │ -C45E1 UID 00000000 (0) │ │ │ │ -C45E5 GID Size 04 (4) │ │ │ │ -C45E6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C45EA CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -C45EE Created Zip Spec 3D (61) '6.1' │ │ │ │ -C45EF Created OS 03 (3) 'Unix' │ │ │ │ -C45F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C45F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C45F2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C45F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C45F6 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C45FA CRC F0556E9A (4032130714) │ │ │ │ -C45FE Compressed Size 000152EE (86766) │ │ │ │ -C4602 Uncompressed Size 000159F8 (88568) │ │ │ │ -C4606 Filename Length 001E (30) │ │ │ │ -C4608 Extra Length 0018 (24) │ │ │ │ -C460A Comment Length 0000 (0) │ │ │ │ -C460C Disk Start 0000 (0) │ │ │ │ -C460E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4610 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4614 Local Header Offset 0009C33B (639803) │ │ │ │ -C4618 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4618: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4636 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4638 Length 0005 (5) │ │ │ │ -C463A Flags 01 (1) 'Modification' │ │ │ │ -C463B Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C463F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4641 Length 000B (11) │ │ │ │ -C4643 Version 01 (1) │ │ │ │ -C4644 UID Size 04 (4) │ │ │ │ -C4645 UID 00000000 (0) │ │ │ │ -C4649 GID Size 04 (4) │ │ │ │ -C464A GID 00000000 (0) │ │ │ │ - │ │ │ │ -C464E CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -C4652 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4653 Created OS 03 (3) 'Unix' │ │ │ │ -C4654 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4655 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4656 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4658 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C465A Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C465E CRC F5E2129F (4125233823) │ │ │ │ -C4662 Compressed Size 000016BC (5820) │ │ │ │ -C4666 Uncompressed Size 000016CD (5837) │ │ │ │ -C466A Filename Length 0015 (21) │ │ │ │ -C466C Extra Length 0018 (24) │ │ │ │ -C466E Comment Length 0000 (0) │ │ │ │ -C4670 Disk Start 0000 (0) │ │ │ │ -C4672 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4674 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4678 Local Header Offset 000B1681 (726657) │ │ │ │ -C467C Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC467C: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4691 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4693 Length 0005 (5) │ │ │ │ -C4695 Flags 01 (1) 'Modification' │ │ │ │ -C4696 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C469A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C469C Length 000B (11) │ │ │ │ -C469E Version 01 (1) │ │ │ │ -C469F UID Size 04 (4) │ │ │ │ -C46A0 UID 00000000 (0) │ │ │ │ -C46A4 GID Size 04 (4) │ │ │ │ -C46A5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C46A9 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -C46AD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C46AE Created OS 03 (3) 'Unix' │ │ │ │ -C46AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C46B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C46B1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C46B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C46B5 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C46B9 CRC F5E2129F (4125233823) │ │ │ │ -C46BD Compressed Size 000016BC (5820) │ │ │ │ -C46C1 Uncompressed Size 000016CD (5837) │ │ │ │ -C46C5 Filename Length 001C (28) │ │ │ │ -C46C7 Extra Length 0018 (24) │ │ │ │ -C46C9 Comment Length 0000 (0) │ │ │ │ -C46CB Disk Start 0000 (0) │ │ │ │ -C46CD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C46CF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C46D3 Local Header Offset 000B2D8C (732556) │ │ │ │ -C46D7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC46D7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C46F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C46F5 Length 0005 (5) │ │ │ │ -C46F7 Flags 01 (1) 'Modification' │ │ │ │ -C46F8 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C46FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C46FE Length 000B (11) │ │ │ │ -C4700 Version 01 (1) │ │ │ │ -C4701 UID Size 04 (4) │ │ │ │ -C4702 UID 00000000 (0) │ │ │ │ -C4706 GID Size 04 (4) │ │ │ │ -C4707 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C470B CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -C470F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4710 Created OS 03 (3) 'Unix' │ │ │ │ -C4711 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C4712 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4713 General Purpose Flag 0000 (0) │ │ │ │ -C4715 Compression Method 0000 (0) 'Stored' │ │ │ │ -C4717 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C471B CRC FC95F24B (4237685323) │ │ │ │ -C471F Compressed Size 00001B84 (7044) │ │ │ │ -C4723 Uncompressed Size 00001B84 (7044) │ │ │ │ -C4727 Filename Length 0016 (22) │ │ │ │ -C4729 Extra Length 0018 (24) │ │ │ │ -C472B Comment Length 0000 (0) │ │ │ │ -C472D Disk Start 0000 (0) │ │ │ │ -C472F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4731 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4735 Local Header Offset 000B449E (738462) │ │ │ │ -C4739 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4739: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C474F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4751 Length 0005 (5) │ │ │ │ -C4753 Flags 01 (1) 'Modification' │ │ │ │ -C4754 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4758 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C475A Length 000B (11) │ │ │ │ -C475C Version 01 (1) │ │ │ │ -C475D UID Size 04 (4) │ │ │ │ -C475E UID 00000000 (0) │ │ │ │ -C4762 GID Size 04 (4) │ │ │ │ -C4763 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4767 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -C476B Created Zip Spec 3D (61) '6.1' │ │ │ │ -C476C Created OS 03 (3) 'Unix' │ │ │ │ -C476D Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C476E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C476F General Purpose Flag 0000 (0) │ │ │ │ -C4771 Compression Method 0000 (0) 'Stored' │ │ │ │ -C4773 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4777 CRC D0D71F86 (3503759238) │ │ │ │ -C477B Compressed Size 00000B7B (2939) │ │ │ │ -C477F Uncompressed Size 00000B7B (2939) │ │ │ │ -C4783 Filename Length 0016 (22) │ │ │ │ -C4785 Extra Length 0018 (24) │ │ │ │ -C4787 Comment Length 0000 (0) │ │ │ │ -C4789 Disk Start 0000 (0) │ │ │ │ -C478B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C478D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4791 Local Header Offset 000B6072 (745586) │ │ │ │ -C4795 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4795: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C47AB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C47AD Length 0005 (5) │ │ │ │ -C47AF Flags 01 (1) 'Modification' │ │ │ │ -C47B0 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C47B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C47B6 Length 000B (11) │ │ │ │ -C47B8 Version 01 (1) │ │ │ │ -C47B9 UID Size 04 (4) │ │ │ │ -C47BA UID 00000000 (0) │ │ │ │ -C47BE GID Size 04 (4) │ │ │ │ -C47BF GID 00000000 (0) │ │ │ │ - │ │ │ │ -C47C3 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -C47C7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C47C8 Created OS 03 (3) 'Unix' │ │ │ │ -C47C9 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C47CA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C47CB General Purpose Flag 0000 (0) │ │ │ │ -C47CD Compression Method 0000 (0) 'Stored' │ │ │ │ -C47CF Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C47D3 CRC FFF9C4D2 (4294558930) │ │ │ │ -C47D7 Compressed Size 0000138F (5007) │ │ │ │ -C47DB Uncompressed Size 0000138F (5007) │ │ │ │ -C47DF Filename Length 0016 (22) │ │ │ │ -C47E1 Extra Length 0018 (24) │ │ │ │ -C47E3 Comment Length 0000 (0) │ │ │ │ -C47E5 Disk Start 0000 (0) │ │ │ │ -C47E7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C47E9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C47ED Local Header Offset 000B6C3D (748605) │ │ │ │ -C47F1 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC47F1: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4807 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4809 Length 0005 (5) │ │ │ │ -C480B Flags 01 (1) 'Modification' │ │ │ │ -C480C Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4810 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4812 Length 000B (11) │ │ │ │ -C4814 Version 01 (1) │ │ │ │ -C4815 UID Size 04 (4) │ │ │ │ -C4816 UID 00000000 (0) │ │ │ │ -C481A GID Size 04 (4) │ │ │ │ -C481B GID 00000000 (0) │ │ │ │ - │ │ │ │ -C481F CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -C4823 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4824 Created OS 03 (3) 'Unix' │ │ │ │ -C4825 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C4826 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4827 General Purpose Flag 0000 (0) │ │ │ │ -C4829 Compression Method 0000 (0) 'Stored' │ │ │ │ -C482B Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C482F CRC A1037E8E (2701360782) │ │ │ │ -C4833 Compressed Size 0000145E (5214) │ │ │ │ -C4837 Uncompressed Size 0000145E (5214) │ │ │ │ -C483B Filename Length 0016 (22) │ │ │ │ -C483D Extra Length 0018 (24) │ │ │ │ -C483F Comment Length 0000 (0) │ │ │ │ -C4841 Disk Start 0000 (0) │ │ │ │ -C4843 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4845 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4849 Local Header Offset 000B801C (753692) │ │ │ │ -C484D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC484D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4863 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4865 Length 0005 (5) │ │ │ │ -C4867 Flags 01 (1) 'Modification' │ │ │ │ -C4868 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C486C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C486E Length 000B (11) │ │ │ │ -C4870 Version 01 (1) │ │ │ │ -C4871 UID Size 04 (4) │ │ │ │ -C4872 UID 00000000 (0) │ │ │ │ -C4876 GID Size 04 (4) │ │ │ │ -C4877 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C487B CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ -C487F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4880 Created OS 03 (3) 'Unix' │ │ │ │ -C4881 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C4882 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4883 General Purpose Flag 0000 (0) │ │ │ │ -C4885 Compression Method 0000 (0) 'Stored' │ │ │ │ -C4887 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C488B CRC 5E9E64F1 (1587438833) │ │ │ │ -C488F Compressed Size 000008EC (2284) │ │ │ │ -C4893 Uncompressed Size 000008EC (2284) │ │ │ │ -C4897 Filename Length 0016 (22) │ │ │ │ -C4899 Extra Length 0018 (24) │ │ │ │ -C489B Comment Length 0000 (0) │ │ │ │ -C489D Disk Start 0000 (0) │ │ │ │ -C489F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C48A1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C48A5 Local Header Offset 000B94CA (758986) │ │ │ │ -C48A9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC48A9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C48BF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C48C1 Length 0005 (5) │ │ │ │ -C48C3 Flags 01 (1) 'Modification' │ │ │ │ -C48C4 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C48C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C48CA Length 000B (11) │ │ │ │ -C48CC Version 01 (1) │ │ │ │ -C48CD UID Size 04 (4) │ │ │ │ -C48CE UID 00000000 (0) │ │ │ │ -C48D2 GID Size 04 (4) │ │ │ │ -C48D3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C48D7 CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ -C48DB Created Zip Spec 3D (61) '6.1' │ │ │ │ -C48DC Created OS 03 (3) 'Unix' │ │ │ │ -C48DD Extract Zip Spec 0A (10) '1.0' │ │ │ │ -C48DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C48DF General Purpose Flag 0000 (0) │ │ │ │ -C48E1 Compression Method 0000 (0) 'Stored' │ │ │ │ -C48E3 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C48E7 CRC 42E340AB (1122189483) │ │ │ │ -C48EB Compressed Size 00001F2E (7982) │ │ │ │ -C48EF Uncompressed Size 00001F2E (7982) │ │ │ │ -C48F3 Filename Length 001E (30) │ │ │ │ -C48F5 Extra Length 0018 (24) │ │ │ │ -C48F7 Comment Length 0000 (0) │ │ │ │ -C48F9 Disk Start 0000 (0) │ │ │ │ -C48FB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C48FD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4901 Local Header Offset 000B9E06 (761350) │ │ │ │ -C4905 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4905: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4923 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4925 Length 0005 (5) │ │ │ │ -C4927 Flags 01 (1) 'Modification' │ │ │ │ -C4928 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C492C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C492E Length 000B (11) │ │ │ │ -C4930 Version 01 (1) │ │ │ │ -C4931 UID Size 04 (4) │ │ │ │ -C4932 UID 00000000 (0) │ │ │ │ -C4936 GID Size 04 (4) │ │ │ │ -C4937 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C493B CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ -C493F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4940 Created OS 03 (3) 'Unix' │ │ │ │ -C4941 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4942 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4943 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4945 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4947 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C494B CRC 29EA8E7B (703237755) │ │ │ │ -C494F Compressed Size 00003D82 (15746) │ │ │ │ -C4953 Uncompressed Size 000166B0 (91824) │ │ │ │ -C4957 Filename Length 001A (26) │ │ │ │ -C4959 Extra Length 0018 (24) │ │ │ │ -C495B Comment Length 0000 (0) │ │ │ │ -C495D Disk Start 0000 (0) │ │ │ │ -C495F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4961 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4965 Local Header Offset 000BBD8C (769420) │ │ │ │ -C4969 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4969: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4983 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4985 Length 0005 (5) │ │ │ │ -C4987 Flags 01 (1) 'Modification' │ │ │ │ -C4988 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C498C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C498E Length 000B (11) │ │ │ │ -C4990 Version 01 (1) │ │ │ │ -C4991 UID Size 04 (4) │ │ │ │ -C4992 UID 00000000 (0) │ │ │ │ -C4996 GID Size 04 (4) │ │ │ │ -C4997 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C499B CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ -C499F Created Zip Spec 3D (61) '6.1' │ │ │ │ -C49A0 Created OS 03 (3) 'Unix' │ │ │ │ -C49A1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C49A2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C49A3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C49A5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C49A7 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C49AB CRC 207E153D (545133885) │ │ │ │ -C49AF Compressed Size 000029D2 (10706) │ │ │ │ -C49B3 Uncompressed Size 0000BB3A (47930) │ │ │ │ -C49B7 Filename Length 0018 (24) │ │ │ │ -C49B9 Extra Length 0018 (24) │ │ │ │ -C49BB Comment Length 0000 (0) │ │ │ │ -C49BD Disk Start 0000 (0) │ │ │ │ -C49BF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C49C1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C49C5 Local Header Offset 000BFB62 (785250) │ │ │ │ -C49C9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC49C9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C49E1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C49E3 Length 0005 (5) │ │ │ │ -C49E5 Flags 01 (1) 'Modification' │ │ │ │ -C49E6 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C49EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C49EC Length 000B (11) │ │ │ │ -C49EE Version 01 (1) │ │ │ │ -C49EF UID Size 04 (4) │ │ │ │ -C49F0 UID 00000000 (0) │ │ │ │ -C49F4 GID Size 04 (4) │ │ │ │ -C49F5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C49F9 CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ -C49FD Created Zip Spec 3D (61) '6.1' │ │ │ │ -C49FE Created OS 03 (3) 'Unix' │ │ │ │ -C49FF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4A00 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4A01 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4A03 Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4A05 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4A09 CRC DCB3B516 (3702764822) │ │ │ │ -C4A0D Compressed Size 000000AE (174) │ │ │ │ -C4A11 Uncompressed Size 000000FC (252) │ │ │ │ -C4A15 Filename Length 0016 (22) │ │ │ │ -C4A17 Extra Length 0018 (24) │ │ │ │ -C4A19 Comment Length 0000 (0) │ │ │ │ -C4A1B Disk Start 0000 (0) │ │ │ │ -C4A1D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4A1F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4A23 Local Header Offset 000C2586 (796038) │ │ │ │ -C4A27 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4A27: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4A3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4A3F Length 0005 (5) │ │ │ │ -C4A41 Flags 01 (1) 'Modification' │ │ │ │ -C4A42 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4A46 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4A48 Length 000B (11) │ │ │ │ -C4A4A Version 01 (1) │ │ │ │ -C4A4B UID Size 04 (4) │ │ │ │ -C4A4C UID 00000000 (0) │ │ │ │ -C4A50 GID Size 04 (4) │ │ │ │ -C4A51 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4A55 CENTRAL HEADER #97 02014B50 (33639248) │ │ │ │ -C4A59 Created Zip Spec 3D (61) '6.1' │ │ │ │ -C4A5A Created OS 03 (3) 'Unix' │ │ │ │ -C4A5B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -C4A5C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -C4A5D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -C4A5F Compression Method 0008 (8) 'Deflated' │ │ │ │ -C4A61 Modification Time 5C8FA0E4 (1552916708) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4A65 CRC 58439733 (1480824627) │ │ │ │ -C4A69 Compressed Size 00000077 (119) │ │ │ │ -C4A6D Uncompressed Size 000000A2 (162) │ │ │ │ -C4A71 Filename Length 002D (45) │ │ │ │ -C4A73 Extra Length 0018 (24) │ │ │ │ -C4A75 Comment Length 0000 (0) │ │ │ │ -C4A77 Disk Start 0000 (0) │ │ │ │ -C4A79 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -C4A7B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -C4A7F Local Header Offset 000C2684 (796292) │ │ │ │ -C4A83 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC4A83: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -C4AB0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -C4AB2 Length 0005 (5) │ │ │ │ -C4AB4 Flags 01 (1) 'Modification' │ │ │ │ -C4AB5 Modification Time 69DFEFEC (1776283628) 'Wed Apr 15 20:07:08 2026' │ │ │ │ -C4AB9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -C4ABB Length 000B (11) │ │ │ │ -C4ABD Version 01 (1) │ │ │ │ -C4ABE UID Size 04 (4) │ │ │ │ -C4ABF UID 00000000 (0) │ │ │ │ -C4AC3 GID Size 04 (4) │ │ │ │ -C4AC4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -C4AC8 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -C4ACC Number of this disk 0000 (0) │ │ │ │ -C4ACE Central Dir Disk no 0000 (0) │ │ │ │ -C4AD0 Entries in this disk 0061 (97) │ │ │ │ -C4AD2 Total Entries 0061 (97) │ │ │ │ -C4AD4 Size of Central Dir 00002366 (9062) │ │ │ │ -C4AD8 Offset to Central Dir 000C2762 (796514) │ │ │ │ -C4ADC Comment Length 0000 (0) │ │ │ │ +C2692 LOCAL HEADER #97 04034B50 (67324752) │ │ │ │ +C2696 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2697 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2698 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C269A Compression Method 0008 (8) 'Deflated' │ │ │ │ +C269C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C26A0 CRC 58439733 (1480824627) │ │ │ │ +C26A4 Compressed Size 00000077 (119) │ │ │ │ +C26A8 Uncompressed Size 000000A2 (162) │ │ │ │ +C26AC Filename Length 002D (45) │ │ │ │ +C26AE Extra Length 001C (28) │ │ │ │ +C26B0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC26B0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C26DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C26DF Length 0009 (9) │ │ │ │ +C26E1 Flags 03 (3) 'Modification Access' │ │ │ │ +C26E2 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C26E6 Access Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C26EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C26EC Length 000B (11) │ │ │ │ +C26EE Version 01 (1) │ │ │ │ +C26EF UID Size 04 (4) │ │ │ │ +C26F0 UID 00000000 (0) │ │ │ │ +C26F4 GID Size 04 (4) │ │ │ │ +C26F5 GID 00000000 (0) │ │ │ │ +C26F9 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +C2770 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +C2774 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2775 Created OS 03 (3) 'Unix' │ │ │ │ +C2776 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C2777 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2778 General Purpose Flag 0000 (0) │ │ │ │ +C277A Compression Method 0000 (0) 'Stored' │ │ │ │ +C277C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2780 CRC 2CAB616F (749429103) │ │ │ │ +C2784 Compressed Size 00000014 (20) │ │ │ │ +C2788 Uncompressed Size 00000014 (20) │ │ │ │ +C278C Filename Length 0008 (8) │ │ │ │ +C278E Extra Length 0018 (24) │ │ │ │ +C2790 Comment Length 0000 (0) │ │ │ │ +C2792 Disk Start 0000 (0) │ │ │ │ +C2794 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2796 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C279A Local Header Offset 00000000 (0) │ │ │ │ +C279E Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC279E: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C27A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C27A8 Length 0005 (5) │ │ │ │ +C27AA Flags 01 (1) 'Modification' │ │ │ │ +C27AB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C27AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C27B1 Length 000B (11) │ │ │ │ +C27B3 Version 01 (1) │ │ │ │ +C27B4 UID Size 04 (4) │ │ │ │ +C27B5 UID 00000000 (0) │ │ │ │ +C27B9 GID Size 04 (4) │ │ │ │ +C27BA GID 00000000 (0) │ │ │ │ + │ │ │ │ +C27BE CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +C27C2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C27C3 Created OS 03 (3) 'Unix' │ │ │ │ +C27C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C27C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C27C6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C27C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C27CA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C27CE CRC 53A58720 (1403356960) │ │ │ │ +C27D2 Compressed Size 00000D22 (3362) │ │ │ │ +C27D6 Uncompressed Size 00003933 (14643) │ │ │ │ +C27DA Filename Length 001B (27) │ │ │ │ +C27DC Extra Length 0018 (24) │ │ │ │ +C27DE Comment Length 0000 (0) │ │ │ │ +C27E0 Disk Start 0000 (0) │ │ │ │ +C27E2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C27E4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C27E8 Local Header Offset 00000056 (86) │ │ │ │ +C27EC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC27EC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2807 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2809 Length 0005 (5) │ │ │ │ +C280B Flags 01 (1) 'Modification' │ │ │ │ +C280C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2810 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2812 Length 000B (11) │ │ │ │ +C2814 Version 01 (1) │ │ │ │ +C2815 UID Size 04 (4) │ │ │ │ +C2816 UID 00000000 (0) │ │ │ │ +C281A GID Size 04 (4) │ │ │ │ +C281B GID 00000000 (0) │ │ │ │ + │ │ │ │ +C281F CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +C2823 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2824 Created OS 03 (3) 'Unix' │ │ │ │ +C2825 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2826 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2827 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2829 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C282B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C282F CRC 546A35FD (1416246781) │ │ │ │ +C2833 Compressed Size 000015B1 (5553) │ │ │ │ +C2837 Uncompressed Size 00004605 (17925) │ │ │ │ +C283B Filename Length 0014 (20) │ │ │ │ +C283D Extra Length 0018 (24) │ │ │ │ +C283F Comment Length 0000 (0) │ │ │ │ +C2841 Disk Start 0000 (0) │ │ │ │ +C2843 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2845 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2849 Local Header Offset 00000DCD (3533) │ │ │ │ +C284D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC284D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2861 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2863 Length 0005 (5) │ │ │ │ +C2865 Flags 01 (1) 'Modification' │ │ │ │ +C2866 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C286A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C286C Length 000B (11) │ │ │ │ +C286E Version 01 (1) │ │ │ │ +C286F UID Size 04 (4) │ │ │ │ +C2870 UID 00000000 (0) │ │ │ │ +C2874 GID Size 04 (4) │ │ │ │ +C2875 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2879 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +C287D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C287E Created OS 03 (3) 'Unix' │ │ │ │ +C287F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2880 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2881 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2883 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2885 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2889 CRC 03A4A46E (61121646) │ │ │ │ +C288D Compressed Size 000006D4 (1748) │ │ │ │ +C2891 Uncompressed Size 00001241 (4673) │ │ │ │ +C2895 Filename Length 0013 (19) │ │ │ │ +C2897 Extra Length 0018 (24) │ │ │ │ +C2899 Comment Length 0000 (0) │ │ │ │ +C289B Disk Start 0000 (0) │ │ │ │ +C289D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C289F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C28A3 Local Header Offset 000023CC (9164) │ │ │ │ +C28A7 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC28A7: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C28BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C28BC Length 0005 (5) │ │ │ │ +C28BE Flags 01 (1) 'Modification' │ │ │ │ +C28BF Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C28C3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C28C5 Length 000B (11) │ │ │ │ +C28C7 Version 01 (1) │ │ │ │ +C28C8 UID Size 04 (4) │ │ │ │ +C28C9 UID 00000000 (0) │ │ │ │ +C28CD GID Size 04 (4) │ │ │ │ +C28CE GID 00000000 (0) │ │ │ │ + │ │ │ │ +C28D2 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +C28D6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C28D7 Created OS 03 (3) 'Unix' │ │ │ │ +C28D8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C28D9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C28DA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C28DC Compression Method 0008 (8) 'Deflated' │ │ │ │ +C28DE Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C28E2 CRC 69D3269E (1775445662) │ │ │ │ +C28E6 Compressed Size 00002E70 (11888) │ │ │ │ +C28EA Uncompressed Size 0000D4C1 (54465) │ │ │ │ +C28EE Filename Length 0014 (20) │ │ │ │ +C28F0 Extra Length 0018 (24) │ │ │ │ +C28F2 Comment Length 0000 (0) │ │ │ │ +C28F4 Disk Start 0000 (0) │ │ │ │ +C28F6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C28F8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C28FC Local Header Offset 00002AED (10989) │ │ │ │ +C2900 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2900: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2914 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2916 Length 0005 (5) │ │ │ │ +C2918 Flags 01 (1) 'Modification' │ │ │ │ +C2919 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C291D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C291F Length 000B (11) │ │ │ │ +C2921 Version 01 (1) │ │ │ │ +C2922 UID Size 04 (4) │ │ │ │ +C2923 UID 00000000 (0) │ │ │ │ +C2927 GID Size 04 (4) │ │ │ │ +C2928 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C292C CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +C2930 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2931 Created OS 03 (3) 'Unix' │ │ │ │ +C2932 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2933 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2934 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2936 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2938 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C293C CRC 272DD323 (657314595) │ │ │ │ +C2940 Compressed Size 000003EF (1007) │ │ │ │ +C2944 Uncompressed Size 00000876 (2166) │ │ │ │ +C2948 Filename Length 0014 (20) │ │ │ │ +C294A Extra Length 0018 (24) │ │ │ │ +C294C Comment Length 0000 (0) │ │ │ │ +C294E Disk Start 0000 (0) │ │ │ │ +C2950 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2952 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2956 Local Header Offset 000059AB (22955) │ │ │ │ +C295A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC295A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C296E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2970 Length 0005 (5) │ │ │ │ +C2972 Flags 01 (1) 'Modification' │ │ │ │ +C2973 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2977 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2979 Length 000B (11) │ │ │ │ +C297B Version 01 (1) │ │ │ │ +C297C UID Size 04 (4) │ │ │ │ +C297D UID 00000000 (0) │ │ │ │ +C2981 GID Size 04 (4) │ │ │ │ +C2982 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2986 CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +C298A Created Zip Spec 3D (61) '6.1' │ │ │ │ +C298B Created OS 03 (3) 'Unix' │ │ │ │ +C298C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C298D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C298E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2990 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2992 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2996 CRC 2F07766F (789018223) │ │ │ │ +C299A Compressed Size 000001AD (429) │ │ │ │ +C299E Uncompressed Size 000002FC (764) │ │ │ │ +C29A2 Filename Length 0011 (17) │ │ │ │ +C29A4 Extra Length 0018 (24) │ │ │ │ +C29A6 Comment Length 0000 (0) │ │ │ │ +C29A8 Disk Start 0000 (0) │ │ │ │ +C29AA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C29AC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C29B0 Local Header Offset 00005DE8 (24040) │ │ │ │ +C29B4 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC29B4: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C29C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C29C7 Length 0005 (5) │ │ │ │ +C29C9 Flags 01 (1) 'Modification' │ │ │ │ +C29CA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C29CE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C29D0 Length 000B (11) │ │ │ │ +C29D2 Version 01 (1) │ │ │ │ +C29D3 UID Size 04 (4) │ │ │ │ +C29D4 UID 00000000 (0) │ │ │ │ +C29D8 GID Size 04 (4) │ │ │ │ +C29D9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C29DD CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +C29E1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C29E2 Created OS 03 (3) 'Unix' │ │ │ │ +C29E3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C29E4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C29E5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C29E7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C29E9 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C29ED CRC 2E830899 (780339353) │ │ │ │ +C29F1 Compressed Size 000020BD (8381) │ │ │ │ +C29F5 Uncompressed Size 0000B4B1 (46257) │ │ │ │ +C29F9 Filename Length 001B (27) │ │ │ │ +C29FB Extra Length 0018 (24) │ │ │ │ +C29FD Comment Length 0000 (0) │ │ │ │ +C29FF Disk Start 0000 (0) │ │ │ │ +C2A01 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2A03 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2A07 Local Header Offset 00005FE0 (24544) │ │ │ │ +C2A0B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2A0B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2A26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2A28 Length 0005 (5) │ │ │ │ +C2A2A Flags 01 (1) 'Modification' │ │ │ │ +C2A2B Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2A2F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2A31 Length 000B (11) │ │ │ │ +C2A33 Version 01 (1) │ │ │ │ +C2A34 UID Size 04 (4) │ │ │ │ +C2A35 UID 00000000 (0) │ │ │ │ +C2A39 GID Size 04 (4) │ │ │ │ +C2A3A GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2A3E CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +C2A42 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2A43 Created OS 03 (3) 'Unix' │ │ │ │ +C2A44 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2A45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2A46 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2A48 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2A4A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2A4E CRC 4E72FA06 (1316157958) │ │ │ │ +C2A52 Compressed Size 00000E68 (3688) │ │ │ │ +C2A56 Uncompressed Size 00003097 (12439) │ │ │ │ +C2A5A Filename Length 001D (29) │ │ │ │ +C2A5C Extra Length 0018 (24) │ │ │ │ +C2A5E Comment Length 0000 (0) │ │ │ │ +C2A60 Disk Start 0000 (0) │ │ │ │ +C2A62 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2A64 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2A68 Local Header Offset 000080F2 (33010) │ │ │ │ +C2A6C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2A6C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2A89 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2A8B Length 0005 (5) │ │ │ │ +C2A8D Flags 01 (1) 'Modification' │ │ │ │ +C2A8E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2A92 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2A94 Length 000B (11) │ │ │ │ +C2A96 Version 01 (1) │ │ │ │ +C2A97 UID Size 04 (4) │ │ │ │ +C2A98 UID 00000000 (0) │ │ │ │ +C2A9C GID Size 04 (4) │ │ │ │ +C2A9D GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2AA1 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +C2AA5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2AA6 Created OS 03 (3) 'Unix' │ │ │ │ +C2AA7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2AA8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2AA9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2AAB Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2AAD Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2AB1 CRC F138337A (4046992250) │ │ │ │ +C2AB5 Compressed Size 0000098E (2446) │ │ │ │ +C2AB9 Uncompressed Size 00001D39 (7481) │ │ │ │ +C2ABD Filename Length 0019 (25) │ │ │ │ +C2ABF Extra Length 0018 (24) │ │ │ │ +C2AC1 Comment Length 0000 (0) │ │ │ │ +C2AC3 Disk Start 0000 (0) │ │ │ │ +C2AC5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2AC7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2ACB Local Header Offset 00008FB1 (36785) │ │ │ │ +C2ACF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2ACF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2AE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2AEA Length 0005 (5) │ │ │ │ +C2AEC Flags 01 (1) 'Modification' │ │ │ │ +C2AED Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2AF1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2AF3 Length 000B (11) │ │ │ │ +C2AF5 Version 01 (1) │ │ │ │ +C2AF6 UID Size 04 (4) │ │ │ │ +C2AF7 UID 00000000 (0) │ │ │ │ +C2AFB GID Size 04 (4) │ │ │ │ +C2AFC GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2B00 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +C2B04 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2B05 Created OS 03 (3) 'Unix' │ │ │ │ +C2B06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2B07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2B08 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2B0A Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2B0C Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2B10 CRC 42AE2C47 (1118710855) │ │ │ │ +C2B14 Compressed Size 00003887 (14471) │ │ │ │ +C2B18 Uncompressed Size 0000F81F (63519) │ │ │ │ +C2B1C Filename Length 0015 (21) │ │ │ │ +C2B1E Extra Length 0018 (24) │ │ │ │ +C2B20 Comment Length 0000 (0) │ │ │ │ +C2B22 Disk Start 0000 (0) │ │ │ │ +C2B24 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2B26 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2B2A Local Header Offset 00009992 (39314) │ │ │ │ +C2B2E Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2B2E: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2B43 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2B45 Length 0005 (5) │ │ │ │ +C2B47 Flags 01 (1) 'Modification' │ │ │ │ +C2B48 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2B4C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2B4E Length 000B (11) │ │ │ │ +C2B50 Version 01 (1) │ │ │ │ +C2B51 UID Size 04 (4) │ │ │ │ +C2B52 UID 00000000 (0) │ │ │ │ +C2B56 GID Size 04 (4) │ │ │ │ +C2B57 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2B5B CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +C2B5F Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2B60 Created OS 03 (3) 'Unix' │ │ │ │ +C2B61 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2B62 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2B63 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2B65 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2B67 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2B6B CRC BCFA2578 (3170510200) │ │ │ │ +C2B6F Compressed Size 0000AB01 (43777) │ │ │ │ +C2B73 Uncompressed Size 0003E0D8 (254168) │ │ │ │ +C2B77 Filename Length 0012 (18) │ │ │ │ +C2B79 Extra Length 0018 (24) │ │ │ │ +C2B7B Comment Length 0000 (0) │ │ │ │ +C2B7D Disk Start 0000 (0) │ │ │ │ +C2B7F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2B81 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2B85 Local Header Offset 0000D268 (53864) │ │ │ │ +C2B89 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2B89: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2B9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2B9D Length 0005 (5) │ │ │ │ +C2B9F Flags 01 (1) 'Modification' │ │ │ │ +C2BA0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2BA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2BA6 Length 000B (11) │ │ │ │ +C2BA8 Version 01 (1) │ │ │ │ +C2BA9 UID Size 04 (4) │ │ │ │ +C2BAA UID 00000000 (0) │ │ │ │ +C2BAE GID Size 04 (4) │ │ │ │ +C2BAF GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2BB3 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +C2BB7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2BB8 Created OS 03 (3) 'Unix' │ │ │ │ +C2BB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2BBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2BBB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2BBD Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2BBF Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2BC3 CRC 52E3F286 (1390670470) │ │ │ │ +C2BC7 Compressed Size 00003AF8 (15096) │ │ │ │ +C2BCB Uncompressed Size 0001B421 (111649) │ │ │ │ +C2BCF Filename Length 0015 (21) │ │ │ │ +C2BD1 Extra Length 0018 (24) │ │ │ │ +C2BD3 Comment Length 0000 (0) │ │ │ │ +C2BD5 Disk Start 0000 (0) │ │ │ │ +C2BD7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2BD9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2BDD Local Header Offset 00017DB5 (97717) │ │ │ │ +C2BE1 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2BE1: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2BF6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2BF8 Length 0005 (5) │ │ │ │ +C2BFA Flags 01 (1) 'Modification' │ │ │ │ +C2BFB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2BFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2C01 Length 000B (11) │ │ │ │ +C2C03 Version 01 (1) │ │ │ │ +C2C04 UID Size 04 (4) │ │ │ │ +C2C05 UID 00000000 (0) │ │ │ │ +C2C09 GID Size 04 (4) │ │ │ │ +C2C0A GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2C0E CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +C2C12 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2C13 Created OS 03 (3) 'Unix' │ │ │ │ +C2C14 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2C15 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2C16 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2C18 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2C1A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2C1E CRC DBC5E740 (3687180096) │ │ │ │ +C2C22 Compressed Size 000091CA (37322) │ │ │ │ +C2C26 Uncompressed Size 0003DBD1 (252881) │ │ │ │ +C2C2A Filename Length 0014 (20) │ │ │ │ +C2C2C Extra Length 0018 (24) │ │ │ │ +C2C2E Comment Length 0000 (0) │ │ │ │ +C2C30 Disk Start 0000 (0) │ │ │ │ +C2C32 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2C34 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2C38 Local Header Offset 0001B8FC (112892) │ │ │ │ +C2C3C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2C3C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2C50 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2C52 Length 0005 (5) │ │ │ │ +C2C54 Flags 01 (1) 'Modification' │ │ │ │ +C2C55 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2C59 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2C5B Length 000B (11) │ │ │ │ +C2C5D Version 01 (1) │ │ │ │ +C2C5E UID Size 04 (4) │ │ │ │ +C2C5F UID 00000000 (0) │ │ │ │ +C2C63 GID Size 04 (4) │ │ │ │ +C2C64 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2C68 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +C2C6C Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2C6D Created OS 03 (3) 'Unix' │ │ │ │ +C2C6E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2C6F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2C70 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2C72 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2C74 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2C78 CRC 953AA54E (2503648590) │ │ │ │ +C2C7C Compressed Size 00009BA8 (39848) │ │ │ │ +C2C80 Uncompressed Size 00027CF5 (163061) │ │ │ │ +C2C84 Filename Length 0019 (25) │ │ │ │ +C2C86 Extra Length 0018 (24) │ │ │ │ +C2C88 Comment Length 0000 (0) │ │ │ │ +C2C8A Disk Start 0000 (0) │ │ │ │ +C2C8C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2C8E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2C92 Local Header Offset 00024B14 (150292) │ │ │ │ +C2C96 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2C96: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2CAF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2CB1 Length 0005 (5) │ │ │ │ +C2CB3 Flags 01 (1) 'Modification' │ │ │ │ +C2CB4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2CB8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2CBA Length 000B (11) │ │ │ │ +C2CBC Version 01 (1) │ │ │ │ +C2CBD UID Size 04 (4) │ │ │ │ +C2CBE UID 00000000 (0) │ │ │ │ +C2CC2 GID Size 04 (4) │ │ │ │ +C2CC3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2CC7 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +C2CCB Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2CCC Created OS 03 (3) 'Unix' │ │ │ │ +C2CCD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2CCE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2CCF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2CD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2CD3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2CD7 CRC 36D4AE72 (919907954) │ │ │ │ +C2CDB Compressed Size 00001219 (4633) │ │ │ │ +C2CDF Uncompressed Size 00003C91 (15505) │ │ │ │ +C2CE3 Filename Length 0010 (16) │ │ │ │ +C2CE5 Extra Length 0018 (24) │ │ │ │ +C2CE7 Comment Length 0000 (0) │ │ │ │ +C2CE9 Disk Start 0000 (0) │ │ │ │ +C2CEB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2CED Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2CF1 Local Header Offset 0002E70F (190223) │ │ │ │ +C2CF5 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2CF5: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2D05 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2D07 Length 0005 (5) │ │ │ │ +C2D09 Flags 01 (1) 'Modification' │ │ │ │ +C2D0A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2D0E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2D10 Length 000B (11) │ │ │ │ +C2D12 Version 01 (1) │ │ │ │ +C2D13 UID Size 04 (4) │ │ │ │ +C2D14 UID 00000000 (0) │ │ │ │ +C2D18 GID Size 04 (4) │ │ │ │ +C2D19 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2D1D CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +C2D21 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2D22 Created OS 03 (3) 'Unix' │ │ │ │ +C2D23 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2D24 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2D25 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2D27 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2D29 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2D2D CRC 6E6B7F3A (1852538682) │ │ │ │ +C2D31 Compressed Size 00002A60 (10848) │ │ │ │ +C2D35 Uncompressed Size 000113A7 (70567) │ │ │ │ +C2D39 Filename Length 0016 (22) │ │ │ │ +C2D3B Extra Length 0018 (24) │ │ │ │ +C2D3D Comment Length 0000 (0) │ │ │ │ +C2D3F Disk Start 0000 (0) │ │ │ │ +C2D41 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2D43 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2D47 Local Header Offset 0002F972 (194930) │ │ │ │ +C2D4B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2D4B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2D61 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2D63 Length 0005 (5) │ │ │ │ +C2D65 Flags 01 (1) 'Modification' │ │ │ │ +C2D66 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2D6A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2D6C Length 000B (11) │ │ │ │ +C2D6E Version 01 (1) │ │ │ │ +C2D6F UID Size 04 (4) │ │ │ │ +C2D70 UID 00000000 (0) │ │ │ │ +C2D74 GID Size 04 (4) │ │ │ │ +C2D75 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2D79 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +C2D7D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2D7E Created OS 03 (3) 'Unix' │ │ │ │ +C2D7F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2D80 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2D81 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2D83 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2D85 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2D89 CRC EC084A66 (3959966310) │ │ │ │ +C2D8D Compressed Size 000014D8 (5336) │ │ │ │ +C2D91 Uncompressed Size 0000518D (20877) │ │ │ │ +C2D95 Filename Length 001D (29) │ │ │ │ +C2D97 Extra Length 0018 (24) │ │ │ │ +C2D99 Comment Length 0000 (0) │ │ │ │ +C2D9B Disk Start 0000 (0) │ │ │ │ +C2D9D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2D9F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2DA3 Local Header Offset 00032422 (205858) │ │ │ │ +C2DA7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2DA7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2DC4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2DC6 Length 0005 (5) │ │ │ │ +C2DC8 Flags 01 (1) 'Modification' │ │ │ │ +C2DC9 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2DCD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2DCF Length 000B (11) │ │ │ │ +C2DD1 Version 01 (1) │ │ │ │ +C2DD2 UID Size 04 (4) │ │ │ │ +C2DD3 UID 00000000 (0) │ │ │ │ +C2DD7 GID Size 04 (4) │ │ │ │ +C2DD8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2DDC CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +C2DE0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2DE1 Created OS 03 (3) 'Unix' │ │ │ │ +C2DE2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2DE3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2DE4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2DE6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2DE8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2DEC CRC C2410B8F (3259042703) │ │ │ │ +C2DF0 Compressed Size 00003805 (14341) │ │ │ │ +C2DF4 Uncompressed Size 0000EA4B (59979) │ │ │ │ +C2DF8 Filename Length 001C (28) │ │ │ │ +C2DFA Extra Length 0018 (24) │ │ │ │ +C2DFC Comment Length 0000 (0) │ │ │ │ +C2DFE Disk Start 0000 (0) │ │ │ │ +C2E00 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2E02 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2E06 Local Header Offset 00033951 (211281) │ │ │ │ +C2E0A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2E0A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2E26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2E28 Length 0005 (5) │ │ │ │ +C2E2A Flags 01 (1) 'Modification' │ │ │ │ +C2E2B Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2E2F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2E31 Length 000B (11) │ │ │ │ +C2E33 Version 01 (1) │ │ │ │ +C2E34 UID Size 04 (4) │ │ │ │ +C2E35 UID 00000000 (0) │ │ │ │ +C2E39 GID Size 04 (4) │ │ │ │ +C2E3A GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2E3E CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +C2E42 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2E43 Created OS 03 (3) 'Unix' │ │ │ │ +C2E44 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2E45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2E46 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2E48 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2E4A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2E4E CRC 958EC234 (2509161012) │ │ │ │ +C2E52 Compressed Size 0000069E (1694) │ │ │ │ +C2E56 Uncompressed Size 000011F3 (4595) │ │ │ │ +C2E5A Filename Length 001C (28) │ │ │ │ +C2E5C Extra Length 0018 (24) │ │ │ │ +C2E5E Comment Length 0000 (0) │ │ │ │ +C2E60 Disk Start 0000 (0) │ │ │ │ +C2E62 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2E64 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2E68 Local Header Offset 000371AC (225708) │ │ │ │ +C2E6C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2E6C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2E88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2E8A Length 0005 (5) │ │ │ │ +C2E8C Flags 01 (1) 'Modification' │ │ │ │ +C2E8D Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2E91 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2E93 Length 000B (11) │ │ │ │ +C2E95 Version 01 (1) │ │ │ │ +C2E96 UID Size 04 (4) │ │ │ │ +C2E97 UID 00000000 (0) │ │ │ │ +C2E9B GID Size 04 (4) │ │ │ │ +C2E9C GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2EA0 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +C2EA4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2EA5 Created OS 03 (3) 'Unix' │ │ │ │ +C2EA6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2EA7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2EA8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2EAA Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2EAC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2EB0 CRC EE3DA990 (3997018512) │ │ │ │ +C2EB4 Compressed Size 0000107C (4220) │ │ │ │ +C2EB8 Uncompressed Size 00004BFE (19454) │ │ │ │ +C2EBC Filename Length 001B (27) │ │ │ │ +C2EBE Extra Length 0018 (24) │ │ │ │ +C2EC0 Comment Length 0000 (0) │ │ │ │ +C2EC2 Disk Start 0000 (0) │ │ │ │ +C2EC4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2EC6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2ECA Local Header Offset 000378A0 (227488) │ │ │ │ +C2ECE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2ECE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2EE9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2EEB Length 0005 (5) │ │ │ │ +C2EED Flags 01 (1) 'Modification' │ │ │ │ +C2EEE Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2EF2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2EF4 Length 000B (11) │ │ │ │ +C2EF6 Version 01 (1) │ │ │ │ +C2EF7 UID Size 04 (4) │ │ │ │ +C2EF8 UID 00000000 (0) │ │ │ │ +C2EFC GID Size 04 (4) │ │ │ │ +C2EFD GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2F01 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +C2F05 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2F06 Created OS 03 (3) 'Unix' │ │ │ │ +C2F07 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2F08 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2F09 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2F0B Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2F0D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2F11 CRC 045E51FF (73290239) │ │ │ │ +C2F15 Compressed Size 00003B3B (15163) │ │ │ │ +C2F19 Uncompressed Size 0000D491 (54417) │ │ │ │ +C2F1D Filename Length 001D (29) │ │ │ │ +C2F1F Extra Length 0018 (24) │ │ │ │ +C2F21 Comment Length 0000 (0) │ │ │ │ +C2F23 Disk Start 0000 (0) │ │ │ │ +C2F25 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2F27 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2F2B Local Header Offset 00038971 (231793) │ │ │ │ +C2F2F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2F2F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2F4C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2F4E Length 0005 (5) │ │ │ │ +C2F50 Flags 01 (1) 'Modification' │ │ │ │ +C2F51 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2F55 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2F57 Length 000B (11) │ │ │ │ +C2F59 Version 01 (1) │ │ │ │ +C2F5A UID Size 04 (4) │ │ │ │ +C2F5B UID 00000000 (0) │ │ │ │ +C2F5F GID Size 04 (4) │ │ │ │ +C2F60 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2F64 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +C2F68 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2F69 Created OS 03 (3) 'Unix' │ │ │ │ +C2F6A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2F6B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2F6C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2F6E Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2F70 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2F74 CRC C52CC238 (3308044856) │ │ │ │ +C2F78 Compressed Size 00000D6A (3434) │ │ │ │ +C2F7C Uncompressed Size 0000388A (14474) │ │ │ │ +C2F80 Filename Length 001D (29) │ │ │ │ +C2F82 Extra Length 0018 (24) │ │ │ │ +C2F84 Comment Length 0000 (0) │ │ │ │ +C2F86 Disk Start 0000 (0) │ │ │ │ +C2F88 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2F8A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2F8E Local Header Offset 0003C503 (247043) │ │ │ │ +C2F92 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2F92: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C2FAF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C2FB1 Length 0005 (5) │ │ │ │ +C2FB3 Flags 01 (1) 'Modification' │ │ │ │ +C2FB4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C2FB8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C2FBA Length 000B (11) │ │ │ │ +C2FBC Version 01 (1) │ │ │ │ +C2FBD UID Size 04 (4) │ │ │ │ +C2FBE UID 00000000 (0) │ │ │ │ +C2FC2 GID Size 04 (4) │ │ │ │ +C2FC3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C2FC7 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +C2FCB Created Zip Spec 3D (61) '6.1' │ │ │ │ +C2FCC Created OS 03 (3) 'Unix' │ │ │ │ +C2FCD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C2FCE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C2FCF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C2FD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C2FD3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C2FD7 CRC 0BFFA3F1 (201303025) │ │ │ │ +C2FDB Compressed Size 00001C89 (7305) │ │ │ │ +C2FDF Uncompressed Size 0000C038 (49208) │ │ │ │ +C2FE3 Filename Length 001A (26) │ │ │ │ +C2FE5 Extra Length 0018 (24) │ │ │ │ +C2FE7 Comment Length 0000 (0) │ │ │ │ +C2FE9 Disk Start 0000 (0) │ │ │ │ +C2FEB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C2FED Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C2FF1 Local Header Offset 0003D2C4 (250564) │ │ │ │ +C2FF5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC2FF5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C300F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3011 Length 0005 (5) │ │ │ │ +C3013 Flags 01 (1) 'Modification' │ │ │ │ +C3014 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3018 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C301A Length 000B (11) │ │ │ │ +C301C Version 01 (1) │ │ │ │ +C301D UID Size 04 (4) │ │ │ │ +C301E UID 00000000 (0) │ │ │ │ +C3022 GID Size 04 (4) │ │ │ │ +C3023 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3027 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +C302B Created Zip Spec 3D (61) '6.1' │ │ │ │ +C302C Created OS 03 (3) 'Unix' │ │ │ │ +C302D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C302E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C302F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3031 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3033 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3037 CRC 478B6B96 (1200319382) │ │ │ │ +C303B Compressed Size 000003DF (991) │ │ │ │ +C303F Uncompressed Size 00000935 (2357) │ │ │ │ +C3043 Filename Length 0012 (18) │ │ │ │ +C3045 Extra Length 0018 (24) │ │ │ │ +C3047 Comment Length 0000 (0) │ │ │ │ +C3049 Disk Start 0000 (0) │ │ │ │ +C304B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C304D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3051 Local Header Offset 0003EFA1 (257953) │ │ │ │ +C3055 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3055: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3067 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3069 Length 0005 (5) │ │ │ │ +C306B Flags 01 (1) 'Modification' │ │ │ │ +C306C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3070 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3072 Length 000B (11) │ │ │ │ +C3074 Version 01 (1) │ │ │ │ +C3075 UID Size 04 (4) │ │ │ │ +C3076 UID 00000000 (0) │ │ │ │ +C307A GID Size 04 (4) │ │ │ │ +C307B GID 00000000 (0) │ │ │ │ + │ │ │ │ +C307F CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +C3083 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3084 Created OS 03 (3) 'Unix' │ │ │ │ +C3085 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3086 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3087 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3089 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C308B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C308F CRC E79489F0 (3885271536) │ │ │ │ +C3093 Compressed Size 000001D3 (467) │ │ │ │ +C3097 Uncompressed Size 00000311 (785) │ │ │ │ +C309B Filename Length 0020 (32) │ │ │ │ +C309D Extra Length 0018 (24) │ │ │ │ +C309F Comment Length 0000 (0) │ │ │ │ +C30A1 Disk Start 0000 (0) │ │ │ │ +C30A3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C30A5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C30A9 Local Header Offset 0003F3CC (259020) │ │ │ │ +C30AD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC30AD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C30CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C30CF Length 0005 (5) │ │ │ │ +C30D1 Flags 01 (1) 'Modification' │ │ │ │ +C30D2 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C30D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C30D8 Length 000B (11) │ │ │ │ +C30DA Version 01 (1) │ │ │ │ +C30DB UID Size 04 (4) │ │ │ │ +C30DC UID 00000000 (0) │ │ │ │ +C30E0 GID Size 04 (4) │ │ │ │ +C30E1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C30E5 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +C30E9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C30EA Created OS 03 (3) 'Unix' │ │ │ │ +C30EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C30EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C30ED General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C30EF Compression Method 0008 (8) 'Deflated' │ │ │ │ +C30F1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C30F5 CRC 73552FFE (1934962686) │ │ │ │ +C30F9 Compressed Size 000017AB (6059) │ │ │ │ +C30FD Uncompressed Size 00009D1B (40219) │ │ │ │ +C3101 Filename Length 001B (27) │ │ │ │ +C3103 Extra Length 0018 (24) │ │ │ │ +C3105 Comment Length 0000 (0) │ │ │ │ +C3107 Disk Start 0000 (0) │ │ │ │ +C3109 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C310B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C310F Local Header Offset 0003F5F9 (259577) │ │ │ │ +C3113 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3113: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C312E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3130 Length 0005 (5) │ │ │ │ +C3132 Flags 01 (1) 'Modification' │ │ │ │ +C3133 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3137 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3139 Length 000B (11) │ │ │ │ +C313B Version 01 (1) │ │ │ │ +C313C UID Size 04 (4) │ │ │ │ +C313D UID 00000000 (0) │ │ │ │ +C3141 GID Size 04 (4) │ │ │ │ +C3142 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3146 CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +C314A Created Zip Spec 3D (61) '6.1' │ │ │ │ +C314B Created OS 03 (3) 'Unix' │ │ │ │ +C314C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C314D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C314E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3150 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3152 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3156 CRC 09EB9A96 (166435478) │ │ │ │ +C315A Compressed Size 0000136D (4973) │ │ │ │ +C315E Uncompressed Size 00003B58 (15192) │ │ │ │ +C3162 Filename Length 0015 (21) │ │ │ │ +C3164 Extra Length 0018 (24) │ │ │ │ +C3166 Comment Length 0000 (0) │ │ │ │ +C3168 Disk Start 0000 (0) │ │ │ │ +C316A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C316C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3170 Local Header Offset 00040DF9 (265721) │ │ │ │ +C3174 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3174: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3189 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C318B Length 0005 (5) │ │ │ │ +C318D Flags 01 (1) 'Modification' │ │ │ │ +C318E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3192 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3194 Length 000B (11) │ │ │ │ +C3196 Version 01 (1) │ │ │ │ +C3197 UID Size 04 (4) │ │ │ │ +C3198 UID 00000000 (0) │ │ │ │ +C319C GID Size 04 (4) │ │ │ │ +C319D GID 00000000 (0) │ │ │ │ + │ │ │ │ +C31A1 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +C31A5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C31A6 Created OS 03 (3) 'Unix' │ │ │ │ +C31A7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C31A8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C31A9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C31AB Compression Method 0008 (8) 'Deflated' │ │ │ │ +C31AD Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C31B1 CRC F9EDEEDB (4193119963) │ │ │ │ +C31B5 Compressed Size 00000AC9 (2761) │ │ │ │ +C31B9 Uncompressed Size 00002133 (8499) │ │ │ │ +C31BD Filename Length 0011 (17) │ │ │ │ +C31BF Extra Length 0018 (24) │ │ │ │ +C31C1 Comment Length 0000 (0) │ │ │ │ +C31C3 Disk Start 0000 (0) │ │ │ │ +C31C5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C31C7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C31CB Local Header Offset 000421B5 (270773) │ │ │ │ +C31CF Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC31CF: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C31E0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C31E2 Length 0005 (5) │ │ │ │ +C31E4 Flags 01 (1) 'Modification' │ │ │ │ +C31E5 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C31E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C31EB Length 000B (11) │ │ │ │ +C31ED Version 01 (1) │ │ │ │ +C31EE UID Size 04 (4) │ │ │ │ +C31EF UID 00000000 (0) │ │ │ │ +C31F3 GID Size 04 (4) │ │ │ │ +C31F4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C31F8 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +C31FC Created Zip Spec 3D (61) '6.1' │ │ │ │ +C31FD Created OS 03 (3) 'Unix' │ │ │ │ +C31FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C31FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3200 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3202 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3204 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3208 CRC 78F661F4 (2029412852) │ │ │ │ +C320C Compressed Size 000003FE (1022) │ │ │ │ +C3210 Uncompressed Size 00000F0C (3852) │ │ │ │ +C3214 Filename Length 0014 (20) │ │ │ │ +C3216 Extra Length 0018 (24) │ │ │ │ +C3218 Comment Length 0000 (0) │ │ │ │ +C321A Disk Start 0000 (0) │ │ │ │ +C321C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C321E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3222 Local Header Offset 00042CC9 (273609) │ │ │ │ +C3226 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3226: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C323A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C323C Length 0005 (5) │ │ │ │ +C323E Flags 01 (1) 'Modification' │ │ │ │ +C323F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3243 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3245 Length 000B (11) │ │ │ │ +C3247 Version 01 (1) │ │ │ │ +C3248 UID Size 04 (4) │ │ │ │ +C3249 UID 00000000 (0) │ │ │ │ +C324D GID Size 04 (4) │ │ │ │ +C324E GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3252 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +C3256 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3257 Created OS 03 (3) 'Unix' │ │ │ │ +C3258 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3259 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C325A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C325C Compression Method 0008 (8) 'Deflated' │ │ │ │ +C325E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3262 CRC 3F5B0F93 (1062932371) │ │ │ │ +C3266 Compressed Size 00001260 (4704) │ │ │ │ +C326A Uncompressed Size 0000346B (13419) │ │ │ │ +C326E Filename Length 0014 (20) │ │ │ │ +C3270 Extra Length 0018 (24) │ │ │ │ +C3272 Comment Length 0000 (0) │ │ │ │ +C3274 Disk Start 0000 (0) │ │ │ │ +C3276 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3278 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C327C Local Header Offset 00043115 (274709) │ │ │ │ +C3280 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3280: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3294 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3296 Length 0005 (5) │ │ │ │ +C3298 Flags 01 (1) 'Modification' │ │ │ │ +C3299 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C329D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C329F Length 000B (11) │ │ │ │ +C32A1 Version 01 (1) │ │ │ │ +C32A2 UID Size 04 (4) │ │ │ │ +C32A3 UID 00000000 (0) │ │ │ │ +C32A7 GID Size 04 (4) │ │ │ │ +C32A8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C32AC CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +C32B0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C32B1 Created OS 03 (3) 'Unix' │ │ │ │ +C32B2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C32B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C32B4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C32B6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C32B8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C32BC CRC 06606451 (106980433) │ │ │ │ +C32C0 Compressed Size 00000ACF (2767) │ │ │ │ +C32C4 Uncompressed Size 000022FF (8959) │ │ │ │ +C32C8 Filename Length 001B (27) │ │ │ │ +C32CA Extra Length 0018 (24) │ │ │ │ +C32CC Comment Length 0000 (0) │ │ │ │ +C32CE Disk Start 0000 (0) │ │ │ │ +C32D0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C32D2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C32D6 Local Header Offset 000443C3 (279491) │ │ │ │ +C32DA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC32DA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C32F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C32F7 Length 0005 (5) │ │ │ │ +C32F9 Flags 01 (1) 'Modification' │ │ │ │ +C32FA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C32FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3300 Length 000B (11) │ │ │ │ +C3302 Version 01 (1) │ │ │ │ +C3303 UID Size 04 (4) │ │ │ │ +C3304 UID 00000000 (0) │ │ │ │ +C3308 GID Size 04 (4) │ │ │ │ +C3309 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C330D CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +C3311 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3312 Created OS 03 (3) 'Unix' │ │ │ │ +C3313 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3314 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3315 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3317 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3319 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C331D CRC 8B6CCC23 (2339163171) │ │ │ │ +C3321 Compressed Size 00000C53 (3155) │ │ │ │ +C3325 Uncompressed Size 00002742 (10050) │ │ │ │ +C3329 Filename Length 0013 (19) │ │ │ │ +C332B Extra Length 0018 (24) │ │ │ │ +C332D Comment Length 0000 (0) │ │ │ │ +C332F Disk Start 0000 (0) │ │ │ │ +C3331 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3333 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3337 Local Header Offset 00044EE7 (282343) │ │ │ │ +C333B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC333B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C334E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3350 Length 0005 (5) │ │ │ │ +C3352 Flags 01 (1) 'Modification' │ │ │ │ +C3353 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3357 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3359 Length 000B (11) │ │ │ │ +C335B Version 01 (1) │ │ │ │ +C335C UID Size 04 (4) │ │ │ │ +C335D UID 00000000 (0) │ │ │ │ +C3361 GID Size 04 (4) │ │ │ │ +C3362 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3366 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +C336A Created Zip Spec 3D (61) '6.1' │ │ │ │ +C336B Created OS 03 (3) 'Unix' │ │ │ │ +C336C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C336D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C336E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3370 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3372 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3376 CRC 75D0E296 (1976623766) │ │ │ │ +C337A Compressed Size 00000C92 (3218) │ │ │ │ +C337E Uncompressed Size 00003D11 (15633) │ │ │ │ +C3382 Filename Length 0014 (20) │ │ │ │ +C3384 Extra Length 0018 (24) │ │ │ │ +C3386 Comment Length 0000 (0) │ │ │ │ +C3388 Disk Start 0000 (0) │ │ │ │ +C338A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C338C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3390 Local Header Offset 00045B87 (285575) │ │ │ │ +C3394 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3394: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C33A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C33AA Length 0005 (5) │ │ │ │ +C33AC Flags 01 (1) 'Modification' │ │ │ │ +C33AD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C33B1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C33B3 Length 000B (11) │ │ │ │ +C33B5 Version 01 (1) │ │ │ │ +C33B6 UID Size 04 (4) │ │ │ │ +C33B7 UID 00000000 (0) │ │ │ │ +C33BB GID Size 04 (4) │ │ │ │ +C33BC GID 00000000 (0) │ │ │ │ + │ │ │ │ +C33C0 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +C33C4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C33C5 Created OS 03 (3) 'Unix' │ │ │ │ +C33C6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C33C7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C33C8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C33CA Compression Method 0008 (8) 'Deflated' │ │ │ │ +C33CC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C33D0 CRC C3A581AC (3282403756) │ │ │ │ +C33D4 Compressed Size 00000F45 (3909) │ │ │ │ +C33D8 Uncompressed Size 00003744 (14148) │ │ │ │ +C33DC Filename Length 000F (15) │ │ │ │ +C33DE Extra Length 0018 (24) │ │ │ │ +C33E0 Comment Length 0000 (0) │ │ │ │ +C33E2 Disk Start 0000 (0) │ │ │ │ +C33E4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C33E6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C33EA Local Header Offset 00046867 (288871) │ │ │ │ +C33EE Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC33EE: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C33FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C33FF Length 0005 (5) │ │ │ │ +C3401 Flags 01 (1) 'Modification' │ │ │ │ +C3402 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3406 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3408 Length 000B (11) │ │ │ │ +C340A Version 01 (1) │ │ │ │ +C340B UID Size 04 (4) │ │ │ │ +C340C UID 00000000 (0) │ │ │ │ +C3410 GID Size 04 (4) │ │ │ │ +C3411 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3415 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ +C3419 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C341A Created OS 03 (3) 'Unix' │ │ │ │ +C341B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C341C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C341D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C341F Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3421 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3425 CRC AD08D6E7 (2903037671) │ │ │ │ +C3429 Compressed Size 000006CE (1742) │ │ │ │ +C342D Uncompressed Size 00001AC4 (6852) │ │ │ │ +C3431 Filename Length 000F (15) │ │ │ │ +C3433 Extra Length 0018 (24) │ │ │ │ +C3435 Comment Length 0000 (0) │ │ │ │ +C3437 Disk Start 0000 (0) │ │ │ │ +C3439 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C343B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C343F Local Header Offset 000477F5 (292853) │ │ │ │ +C3443 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3443: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3452 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3454 Length 0005 (5) │ │ │ │ +C3456 Flags 01 (1) 'Modification' │ │ │ │ +C3457 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C345B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C345D Length 000B (11) │ │ │ │ +C345F Version 01 (1) │ │ │ │ +C3460 UID Size 04 (4) │ │ │ │ +C3461 UID 00000000 (0) │ │ │ │ +C3465 GID Size 04 (4) │ │ │ │ +C3466 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C346A CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +C346E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C346F Created OS 03 (3) 'Unix' │ │ │ │ +C3470 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3471 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3472 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3474 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3476 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C347A CRC 5583AB14 (1434692372) │ │ │ │ +C347E Compressed Size 00001A52 (6738) │ │ │ │ +C3482 Uncompressed Size 0000650E (25870) │ │ │ │ +C3486 Filename Length 0013 (19) │ │ │ │ +C3488 Extra Length 0018 (24) │ │ │ │ +C348A Comment Length 0000 (0) │ │ │ │ +C348C Disk Start 0000 (0) │ │ │ │ +C348E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3490 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3494 Local Header Offset 00047F0C (294668) │ │ │ │ +C3498 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3498: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C34AB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C34AD Length 0005 (5) │ │ │ │ +C34AF Flags 01 (1) 'Modification' │ │ │ │ +C34B0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C34B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C34B6 Length 000B (11) │ │ │ │ +C34B8 Version 01 (1) │ │ │ │ +C34B9 UID Size 04 (4) │ │ │ │ +C34BA UID 00000000 (0) │ │ │ │ +C34BE GID Size 04 (4) │ │ │ │ +C34BF GID 00000000 (0) │ │ │ │ + │ │ │ │ +C34C3 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +C34C7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C34C8 Created OS 03 (3) 'Unix' │ │ │ │ +C34C9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C34CA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C34CB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C34CD Compression Method 0008 (8) 'Deflated' │ │ │ │ +C34CF Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C34D3 CRC 90967BFA (2425781242) │ │ │ │ +C34D7 Compressed Size 000009A6 (2470) │ │ │ │ +C34DB Uncompressed Size 00001B6A (7018) │ │ │ │ +C34DF Filename Length 0010 (16) │ │ │ │ +C34E1 Extra Length 0018 (24) │ │ │ │ +C34E3 Comment Length 0000 (0) │ │ │ │ +C34E5 Disk Start 0000 (0) │ │ │ │ +C34E7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C34E9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C34ED Local Header Offset 000499AB (301483) │ │ │ │ +C34F1 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC34F1: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3501 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3503 Length 0005 (5) │ │ │ │ +C3505 Flags 01 (1) 'Modification' │ │ │ │ +C3506 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C350A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C350C Length 000B (11) │ │ │ │ +C350E Version 01 (1) │ │ │ │ +C350F UID Size 04 (4) │ │ │ │ +C3510 UID 00000000 (0) │ │ │ │ +C3514 GID Size 04 (4) │ │ │ │ +C3515 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3519 CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +C351D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C351E Created OS 03 (3) 'Unix' │ │ │ │ +C351F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3520 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3521 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3523 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3525 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3529 CRC D37ED789 (3548305289) │ │ │ │ +C352D Compressed Size 000006B6 (1718) │ │ │ │ +C3531 Uncompressed Size 00001565 (5477) │ │ │ │ +C3535 Filename Length 0012 (18) │ │ │ │ +C3537 Extra Length 0018 (24) │ │ │ │ +C3539 Comment Length 0000 (0) │ │ │ │ +C353B Disk Start 0000 (0) │ │ │ │ +C353D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C353F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3543 Local Header Offset 0004A39B (304027) │ │ │ │ +C3547 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3547: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3559 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C355B Length 0005 (5) │ │ │ │ +C355D Flags 01 (1) 'Modification' │ │ │ │ +C355E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3562 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3564 Length 000B (11) │ │ │ │ +C3566 Version 01 (1) │ │ │ │ +C3567 UID Size 04 (4) │ │ │ │ +C3568 UID 00000000 (0) │ │ │ │ +C356C GID Size 04 (4) │ │ │ │ +C356D GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3571 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +C3575 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3576 Created OS 03 (3) 'Unix' │ │ │ │ +C3577 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3578 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3579 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C357B Compression Method 0008 (8) 'Deflated' │ │ │ │ +C357D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3581 CRC F24D9270 (4065170032) │ │ │ │ +C3585 Compressed Size 00002D56 (11606) │ │ │ │ +C3589 Uncompressed Size 0000D083 (53379) │ │ │ │ +C358D Filename Length 0010 (16) │ │ │ │ +C358F Extra Length 0018 (24) │ │ │ │ +C3591 Comment Length 0000 (0) │ │ │ │ +C3593 Disk Start 0000 (0) │ │ │ │ +C3595 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3597 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C359B Local Header Offset 0004AA9D (305821) │ │ │ │ +C359F Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC359F: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C35AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C35B1 Length 0005 (5) │ │ │ │ +C35B3 Flags 01 (1) 'Modification' │ │ │ │ +C35B4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C35B8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C35BA Length 000B (11) │ │ │ │ +C35BC Version 01 (1) │ │ │ │ +C35BD UID Size 04 (4) │ │ │ │ +C35BE UID 00000000 (0) │ │ │ │ +C35C2 GID Size 04 (4) │ │ │ │ +C35C3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C35C7 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +C35CB Created Zip Spec 3D (61) '6.1' │ │ │ │ +C35CC Created OS 03 (3) 'Unix' │ │ │ │ +C35CD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C35CE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C35CF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C35D1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C35D3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C35D7 CRC 6DD28D49 (1842515273) │ │ │ │ +C35DB Compressed Size 00001E7F (7807) │ │ │ │ +C35DF Uncompressed Size 00009AAA (39594) │ │ │ │ +C35E3 Filename Length 0012 (18) │ │ │ │ +C35E5 Extra Length 0018 (24) │ │ │ │ +C35E7 Comment Length 0000 (0) │ │ │ │ +C35E9 Disk Start 0000 (0) │ │ │ │ +C35EB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C35ED Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C35F1 Local Header Offset 0004D83D (317501) │ │ │ │ +C35F5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC35F5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3607 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3609 Length 0005 (5) │ │ │ │ +C360B Flags 01 (1) 'Modification' │ │ │ │ +C360C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3610 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3612 Length 000B (11) │ │ │ │ +C3614 Version 01 (1) │ │ │ │ +C3615 UID Size 04 (4) │ │ │ │ +C3616 UID 00000000 (0) │ │ │ │ +C361A GID Size 04 (4) │ │ │ │ +C361B GID 00000000 (0) │ │ │ │ + │ │ │ │ +C361F CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +C3623 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3624 Created OS 03 (3) 'Unix' │ │ │ │ +C3625 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3626 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3627 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3629 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C362B Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C362F CRC 68D3A344 (1758700356) │ │ │ │ +C3633 Compressed Size 00001478 (5240) │ │ │ │ +C3637 Uncompressed Size 00007AD0 (31440) │ │ │ │ +C363B Filename Length 0018 (24) │ │ │ │ +C363D Extra Length 0018 (24) │ │ │ │ +C363F Comment Length 0000 (0) │ │ │ │ +C3641 Disk Start 0000 (0) │ │ │ │ +C3643 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3645 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3649 Local Header Offset 0004F708 (325384) │ │ │ │ +C364D Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC364D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3665 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3667 Length 0005 (5) │ │ │ │ +C3669 Flags 01 (1) 'Modification' │ │ │ │ +C366A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C366E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3670 Length 000B (11) │ │ │ │ +C3672 Version 01 (1) │ │ │ │ +C3673 UID Size 04 (4) │ │ │ │ +C3674 UID 00000000 (0) │ │ │ │ +C3678 GID Size 04 (4) │ │ │ │ +C3679 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C367D CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +C3681 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3682 Created OS 03 (3) 'Unix' │ │ │ │ +C3683 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3684 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3685 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3687 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3689 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C368D CRC BE84805E (3196354654) │ │ │ │ +C3691 Compressed Size 000021E3 (8675) │ │ │ │ +C3695 Uncompressed Size 0000D21D (53789) │ │ │ │ +C3699 Filename Length 001F (31) │ │ │ │ +C369B Extra Length 0018 (24) │ │ │ │ +C369D Comment Length 0000 (0) │ │ │ │ +C369F Disk Start 0000 (0) │ │ │ │ +C36A1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C36A3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C36A7 Local Header Offset 00050BD2 (330706) │ │ │ │ +C36AB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC36AB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C36CA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C36CC Length 0005 (5) │ │ │ │ +C36CE Flags 01 (1) 'Modification' │ │ │ │ +C36CF Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C36D3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C36D5 Length 000B (11) │ │ │ │ +C36D7 Version 01 (1) │ │ │ │ +C36D8 UID Size 04 (4) │ │ │ │ +C36D9 UID 00000000 (0) │ │ │ │ +C36DD GID Size 04 (4) │ │ │ │ +C36DE GID 00000000 (0) │ │ │ │ + │ │ │ │ +C36E2 CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +C36E6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C36E7 Created OS 03 (3) 'Unix' │ │ │ │ +C36E8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C36E9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C36EA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C36EC Compression Method 0008 (8) 'Deflated' │ │ │ │ +C36EE Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C36F2 CRC 585AC976 (1482344822) │ │ │ │ +C36F6 Compressed Size 000003F7 (1015) │ │ │ │ +C36FA Uncompressed Size 000008A3 (2211) │ │ │ │ +C36FE Filename Length 001E (30) │ │ │ │ +C3700 Extra Length 0018 (24) │ │ │ │ +C3702 Comment Length 0000 (0) │ │ │ │ +C3704 Disk Start 0000 (0) │ │ │ │ +C3706 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3708 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C370C Local Header Offset 00052E0E (339470) │ │ │ │ +C3710 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3710: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C372E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3730 Length 0005 (5) │ │ │ │ +C3732 Flags 01 (1) 'Modification' │ │ │ │ +C3733 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3737 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3739 Length 000B (11) │ │ │ │ +C373B Version 01 (1) │ │ │ │ +C373C UID Size 04 (4) │ │ │ │ +C373D UID 00000000 (0) │ │ │ │ +C3741 GID Size 04 (4) │ │ │ │ +C3742 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3746 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +C374A Created Zip Spec 3D (61) '6.1' │ │ │ │ +C374B Created OS 03 (3) 'Unix' │ │ │ │ +C374C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C374D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C374E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3750 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3752 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3756 CRC 16308EDA (372281050) │ │ │ │ +C375A Compressed Size 00004361 (17249) │ │ │ │ +C375E Uncompressed Size 0000E06F (57455) │ │ │ │ +C3762 Filename Length 0013 (19) │ │ │ │ +C3764 Extra Length 0018 (24) │ │ │ │ +C3766 Comment Length 0000 (0) │ │ │ │ +C3768 Disk Start 0000 (0) │ │ │ │ +C376A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C376C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3770 Local Header Offset 0005325D (340573) │ │ │ │ +C3774 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3774: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3787 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3789 Length 0005 (5) │ │ │ │ +C378B Flags 01 (1) 'Modification' │ │ │ │ +C378C Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3790 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3792 Length 000B (11) │ │ │ │ +C3794 Version 01 (1) │ │ │ │ +C3795 UID Size 04 (4) │ │ │ │ +C3796 UID 00000000 (0) │ │ │ │ +C379A GID Size 04 (4) │ │ │ │ +C379B GID 00000000 (0) │ │ │ │ + │ │ │ │ +C379F CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +C37A3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C37A4 Created OS 03 (3) 'Unix' │ │ │ │ +C37A5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C37A6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C37A7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C37A9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C37AB Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C37AF CRC 4B98C7C0 (1268303808) │ │ │ │ +C37B3 Compressed Size 000026C3 (9923) │ │ │ │ +C37B7 Uncompressed Size 00006E45 (28229) │ │ │ │ +C37BB Filename Length 0019 (25) │ │ │ │ +C37BD Extra Length 0018 (24) │ │ │ │ +C37BF Comment Length 0000 (0) │ │ │ │ +C37C1 Disk Start 0000 (0) │ │ │ │ +C37C3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C37C5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C37C9 Local Header Offset 0005760B (357899) │ │ │ │ +C37CD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC37CD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C37E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C37E8 Length 0005 (5) │ │ │ │ +C37EA Flags 01 (1) 'Modification' │ │ │ │ +C37EB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C37EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C37F1 Length 000B (11) │ │ │ │ +C37F3 Version 01 (1) │ │ │ │ +C37F4 UID Size 04 (4) │ │ │ │ +C37F5 UID 00000000 (0) │ │ │ │ +C37F9 GID Size 04 (4) │ │ │ │ +C37FA GID 00000000 (0) │ │ │ │ + │ │ │ │ +C37FE CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +C3802 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3803 Created OS 03 (3) 'Unix' │ │ │ │ +C3804 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3805 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3806 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3808 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C380A Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C380E CRC F8618863 (4167141475) │ │ │ │ +C3812 Compressed Size 00002738 (10040) │ │ │ │ +C3816 Uncompressed Size 00008B83 (35715) │ │ │ │ +C381A Filename Length 0019 (25) │ │ │ │ +C381C Extra Length 0018 (24) │ │ │ │ +C381E Comment Length 0000 (0) │ │ │ │ +C3820 Disk Start 0000 (0) │ │ │ │ +C3822 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3824 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3828 Local Header Offset 00059D21 (367905) │ │ │ │ +C382C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC382C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3845 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3847 Length 0005 (5) │ │ │ │ +C3849 Flags 01 (1) 'Modification' │ │ │ │ +C384A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C384E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3850 Length 000B (11) │ │ │ │ +C3852 Version 01 (1) │ │ │ │ +C3853 UID Size 04 (4) │ │ │ │ +C3854 UID 00000000 (0) │ │ │ │ +C3858 GID Size 04 (4) │ │ │ │ +C3859 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C385D CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +C3861 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3862 Created OS 03 (3) 'Unix' │ │ │ │ +C3863 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3864 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3865 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3867 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3869 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C386D CRC 19093DDC (420036060) │ │ │ │ +C3871 Compressed Size 00000ECC (3788) │ │ │ │ +C3875 Uncompressed Size 000053BF (21439) │ │ │ │ +C3879 Filename Length 0021 (33) │ │ │ │ +C387B Extra Length 0018 (24) │ │ │ │ +C387D Comment Length 0000 (0) │ │ │ │ +C387F Disk Start 0000 (0) │ │ │ │ +C3881 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3883 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3887 Local Header Offset 0005C4AC (378028) │ │ │ │ +C388B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC388B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C38AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C38AE Length 0005 (5) │ │ │ │ +C38B0 Flags 01 (1) 'Modification' │ │ │ │ +C38B1 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C38B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C38B7 Length 000B (11) │ │ │ │ +C38B9 Version 01 (1) │ │ │ │ +C38BA UID Size 04 (4) │ │ │ │ +C38BB UID 00000000 (0) │ │ │ │ +C38BF GID Size 04 (4) │ │ │ │ +C38C0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C38C4 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +C38C8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C38C9 Created OS 03 (3) 'Unix' │ │ │ │ +C38CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C38CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C38CC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C38CE Compression Method 0008 (8) 'Deflated' │ │ │ │ +C38D0 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C38D4 CRC D1EC3113 (3521917203) │ │ │ │ +C38D8 Compressed Size 00000535 (1333) │ │ │ │ +C38DC Uncompressed Size 00000C96 (3222) │ │ │ │ +C38E0 Filename Length 0017 (23) │ │ │ │ +C38E2 Extra Length 0018 (24) │ │ │ │ +C38E4 Comment Length 0000 (0) │ │ │ │ +C38E6 Disk Start 0000 (0) │ │ │ │ +C38E8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C38EA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C38EE Local Header Offset 0005D3D3 (381907) │ │ │ │ +C38F2 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC38F2: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3909 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C390B Length 0005 (5) │ │ │ │ +C390D Flags 01 (1) 'Modification' │ │ │ │ +C390E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3912 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3914 Length 000B (11) │ │ │ │ +C3916 Version 01 (1) │ │ │ │ +C3917 UID Size 04 (4) │ │ │ │ +C3918 UID 00000000 (0) │ │ │ │ +C391C GID Size 04 (4) │ │ │ │ +C391D GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3921 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +C3925 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3926 Created OS 03 (3) 'Unix' │ │ │ │ +C3927 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3928 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3929 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C392B Compression Method 0008 (8) 'Deflated' │ │ │ │ +C392D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3931 CRC 9ED6F2D9 (2664887001) │ │ │ │ +C3935 Compressed Size 00000467 (1127) │ │ │ │ +C3939 Uncompressed Size 00000931 (2353) │ │ │ │ +C393D Filename Length 001B (27) │ │ │ │ +C393F Extra Length 0018 (24) │ │ │ │ +C3941 Comment Length 0000 (0) │ │ │ │ +C3943 Disk Start 0000 (0) │ │ │ │ +C3945 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3947 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C394B Local Header Offset 0005D959 (383321) │ │ │ │ +C394F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC394F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C396A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C396C Length 0005 (5) │ │ │ │ +C396E Flags 01 (1) 'Modification' │ │ │ │ +C396F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3973 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3975 Length 000B (11) │ │ │ │ +C3977 Version 01 (1) │ │ │ │ +C3978 UID Size 04 (4) │ │ │ │ +C3979 UID 00000000 (0) │ │ │ │ +C397D GID Size 04 (4) │ │ │ │ +C397E GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3982 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +C3986 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3987 Created OS 03 (3) 'Unix' │ │ │ │ +C3988 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3989 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C398A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C398C Compression Method 0008 (8) 'Deflated' │ │ │ │ +C398E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3992 CRC 2606F569 (637990249) │ │ │ │ +C3996 Compressed Size 000016F6 (5878) │ │ │ │ +C399A Uncompressed Size 00007A86 (31366) │ │ │ │ +C399E Filename Length 001F (31) │ │ │ │ +C39A0 Extra Length 0018 (24) │ │ │ │ +C39A2 Comment Length 0000 (0) │ │ │ │ +C39A4 Disk Start 0000 (0) │ │ │ │ +C39A6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C39A8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C39AC Local Header Offset 0005DE15 (384533) │ │ │ │ +C39B0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC39B0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C39CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C39D1 Length 0005 (5) │ │ │ │ +C39D3 Flags 01 (1) 'Modification' │ │ │ │ +C39D4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C39D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C39DA Length 000B (11) │ │ │ │ +C39DC Version 01 (1) │ │ │ │ +C39DD UID Size 04 (4) │ │ │ │ +C39DE UID 00000000 (0) │ │ │ │ +C39E2 GID Size 04 (4) │ │ │ │ +C39E3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C39E7 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +C39EB Created Zip Spec 3D (61) '6.1' │ │ │ │ +C39EC Created OS 03 (3) 'Unix' │ │ │ │ +C39ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C39EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C39EF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C39F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C39F3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C39F7 CRC 758BB33D (1972089661) │ │ │ │ +C39FB Compressed Size 00004168 (16744) │ │ │ │ +C39FF Uncompressed Size 0001D163 (119139) │ │ │ │ +C3A03 Filename Length 0010 (16) │ │ │ │ +C3A05 Extra Length 0018 (24) │ │ │ │ +C3A07 Comment Length 0000 (0) │ │ │ │ +C3A09 Disk Start 0000 (0) │ │ │ │ +C3A0B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3A0D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3A11 Local Header Offset 0005F564 (390500) │ │ │ │ +C3A15 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3A15: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3A25 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3A27 Length 0005 (5) │ │ │ │ +C3A29 Flags 01 (1) 'Modification' │ │ │ │ +C3A2A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3A2E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3A30 Length 000B (11) │ │ │ │ +C3A32 Version 01 (1) │ │ │ │ +C3A33 UID Size 04 (4) │ │ │ │ +C3A34 UID 00000000 (0) │ │ │ │ +C3A38 GID Size 04 (4) │ │ │ │ +C3A39 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3A3D CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +C3A41 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3A42 Created OS 03 (3) 'Unix' │ │ │ │ +C3A43 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3A44 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3A45 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3A47 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3A49 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3A4D CRC C55EF92D (3311335725) │ │ │ │ +C3A51 Compressed Size 00000AE8 (2792) │ │ │ │ +C3A55 Uncompressed Size 000021E8 (8680) │ │ │ │ +C3A59 Filename Length 0014 (20) │ │ │ │ +C3A5B Extra Length 0018 (24) │ │ │ │ +C3A5D Comment Length 0000 (0) │ │ │ │ +C3A5F Disk Start 0000 (0) │ │ │ │ +C3A61 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3A63 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3A67 Local Header Offset 00063716 (407318) │ │ │ │ +C3A6B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3A6B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3A7F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3A81 Length 0005 (5) │ │ │ │ +C3A83 Flags 01 (1) 'Modification' │ │ │ │ +C3A84 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3A88 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3A8A Length 000B (11) │ │ │ │ +C3A8C Version 01 (1) │ │ │ │ +C3A8D UID Size 04 (4) │ │ │ │ +C3A8E UID 00000000 (0) │ │ │ │ +C3A92 GID Size 04 (4) │ │ │ │ +C3A93 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3A97 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +C3A9B Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3A9C Created OS 03 (3) 'Unix' │ │ │ │ +C3A9D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3A9E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3A9F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3AA1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3AA3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3AA7 CRC 32543857 (844380247) │ │ │ │ +C3AAB Compressed Size 0000B523 (46371) │ │ │ │ +C3AAF Uncompressed Size 00041755 (268117) │ │ │ │ +C3AB3 Filename Length 0017 (23) │ │ │ │ +C3AB5 Extra Length 0018 (24) │ │ │ │ +C3AB7 Comment Length 0000 (0) │ │ │ │ +C3AB9 Disk Start 0000 (0) │ │ │ │ +C3ABB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3ABD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3AC1 Local Header Offset 0006424C (410188) │ │ │ │ +C3AC5 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3AC5: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3ADC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3ADE Length 0005 (5) │ │ │ │ +C3AE0 Flags 01 (1) 'Modification' │ │ │ │ +C3AE1 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3AE5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3AE7 Length 000B (11) │ │ │ │ +C3AE9 Version 01 (1) │ │ │ │ +C3AEA UID Size 04 (4) │ │ │ │ +C3AEB UID 00000000 (0) │ │ │ │ +C3AEF GID Size 04 (4) │ │ │ │ +C3AF0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3AF4 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +C3AF8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3AF9 Created OS 03 (3) 'Unix' │ │ │ │ +C3AFA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3AFB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3AFC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3AFE Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3B00 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3B04 CRC 564036C9 (1447048905) │ │ │ │ +C3B08 Compressed Size 00000400 (1024) │ │ │ │ +C3B0C Uncompressed Size 0000093D (2365) │ │ │ │ +C3B10 Filename Length 0013 (19) │ │ │ │ +C3B12 Extra Length 0018 (24) │ │ │ │ +C3B14 Comment Length 0000 (0) │ │ │ │ +C3B16 Disk Start 0000 (0) │ │ │ │ +C3B18 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3B1A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3B1E Local Header Offset 0006F7C0 (456640) │ │ │ │ +C3B22 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3B22: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3B35 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3B37 Length 0005 (5) │ │ │ │ +C3B39 Flags 01 (1) 'Modification' │ │ │ │ +C3B3A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3B3E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3B40 Length 000B (11) │ │ │ │ +C3B42 Version 01 (1) │ │ │ │ +C3B43 UID Size 04 (4) │ │ │ │ +C3B44 UID 00000000 (0) │ │ │ │ +C3B48 GID Size 04 (4) │ │ │ │ +C3B49 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3B4D CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +C3B51 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3B52 Created OS 03 (3) 'Unix' │ │ │ │ +C3B53 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3B54 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3B55 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3B57 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3B59 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3B5D CRC 4AD4846E (1255441518) │ │ │ │ +C3B61 Compressed Size 000014D9 (5337) │ │ │ │ +C3B65 Uncompressed Size 00006892 (26770) │ │ │ │ +C3B69 Filename Length 0012 (18) │ │ │ │ +C3B6B Extra Length 0018 (24) │ │ │ │ +C3B6D Comment Length 0000 (0) │ │ │ │ +C3B6F Disk Start 0000 (0) │ │ │ │ +C3B71 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3B73 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3B77 Local Header Offset 0006FC0D (457741) │ │ │ │ +C3B7B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3B7B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3B8D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3B8F Length 0005 (5) │ │ │ │ +C3B91 Flags 01 (1) 'Modification' │ │ │ │ +C3B92 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3B96 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3B98 Length 000B (11) │ │ │ │ +C3B9A Version 01 (1) │ │ │ │ +C3B9B UID Size 04 (4) │ │ │ │ +C3B9C UID 00000000 (0) │ │ │ │ +C3BA0 GID Size 04 (4) │ │ │ │ +C3BA1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3BA5 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +C3BA9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3BAA Created OS 03 (3) 'Unix' │ │ │ │ +C3BAB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3BAC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3BAD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3BAF Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3BB1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3BB5 CRC AB6E6BBC (2876140476) │ │ │ │ +C3BB9 Compressed Size 00001205 (4613) │ │ │ │ +C3BBD Uncompressed Size 0000414F (16719) │ │ │ │ +C3BC1 Filename Length 0012 (18) │ │ │ │ +C3BC3 Extra Length 0018 (24) │ │ │ │ +C3BC5 Comment Length 0000 (0) │ │ │ │ +C3BC7 Disk Start 0000 (0) │ │ │ │ +C3BC9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3BCB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3BCF Local Header Offset 00071132 (463154) │ │ │ │ +C3BD3 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3BD3: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3BE5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3BE7 Length 0005 (5) │ │ │ │ +C3BE9 Flags 01 (1) 'Modification' │ │ │ │ +C3BEA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3BEE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3BF0 Length 000B (11) │ │ │ │ +C3BF2 Version 01 (1) │ │ │ │ +C3BF3 UID Size 04 (4) │ │ │ │ +C3BF4 UID 00000000 (0) │ │ │ │ +C3BF8 GID Size 04 (4) │ │ │ │ +C3BF9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3BFD CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +C3C01 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3C02 Created OS 03 (3) 'Unix' │ │ │ │ +C3C03 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3C04 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3C05 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3C07 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3C09 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3C0D CRC 5C8A4BDC (1552567260) │ │ │ │ +C3C11 Compressed Size 00000704 (1796) │ │ │ │ +C3C15 Uncompressed Size 000011A7 (4519) │ │ │ │ +C3C19 Filename Length 0019 (25) │ │ │ │ +C3C1B Extra Length 0018 (24) │ │ │ │ +C3C1D Comment Length 0000 (0) │ │ │ │ +C3C1F Disk Start 0000 (0) │ │ │ │ +C3C21 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3C23 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3C27 Local Header Offset 00072383 (467843) │ │ │ │ +C3C2B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3C2B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3C44 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3C46 Length 0005 (5) │ │ │ │ +C3C48 Flags 01 (1) 'Modification' │ │ │ │ +C3C49 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3C4D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3C4F Length 000B (11) │ │ │ │ +C3C51 Version 01 (1) │ │ │ │ +C3C52 UID Size 04 (4) │ │ │ │ +C3C53 UID 00000000 (0) │ │ │ │ +C3C57 GID Size 04 (4) │ │ │ │ +C3C58 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3C5C CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +C3C60 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3C61 Created OS 03 (3) 'Unix' │ │ │ │ +C3C62 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3C63 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3C64 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3C66 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3C68 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3C6C CRC 3381C721 (864143137) │ │ │ │ +C3C70 Compressed Size 000018B5 (6325) │ │ │ │ +C3C74 Uncompressed Size 0000A678 (42616) │ │ │ │ +C3C78 Filename Length 0019 (25) │ │ │ │ +C3C7A Extra Length 0018 (24) │ │ │ │ +C3C7C Comment Length 0000 (0) │ │ │ │ +C3C7E Disk Start 0000 (0) │ │ │ │ +C3C80 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3C82 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3C86 Local Header Offset 00072ADA (469722) │ │ │ │ +C3C8A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3C8A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3CA3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3CA5 Length 0005 (5) │ │ │ │ +C3CA7 Flags 01 (1) 'Modification' │ │ │ │ +C3CA8 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3CAC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3CAE Length 000B (11) │ │ │ │ +C3CB0 Version 01 (1) │ │ │ │ +C3CB1 UID Size 04 (4) │ │ │ │ +C3CB2 UID 00000000 (0) │ │ │ │ +C3CB6 GID Size 04 (4) │ │ │ │ +C3CB7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3CBB CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +C3CBF Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3CC0 Created OS 03 (3) 'Unix' │ │ │ │ +C3CC1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3CC2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3CC3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3CC5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3CC7 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3CCB CRC 4D1409E6 (1293158886) │ │ │ │ +C3CCF Compressed Size 0000177C (6012) │ │ │ │ +C3CD3 Uncompressed Size 0000472C (18220) │ │ │ │ +C3CD7 Filename Length 0014 (20) │ │ │ │ +C3CD9 Extra Length 0018 (24) │ │ │ │ +C3CDB Comment Length 0000 (0) │ │ │ │ +C3CDD Disk Start 0000 (0) │ │ │ │ +C3CDF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3CE1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3CE5 Local Header Offset 000743E2 (476130) │ │ │ │ +C3CE9 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3CE9: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3CFD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3CFF Length 0005 (5) │ │ │ │ +C3D01 Flags 01 (1) 'Modification' │ │ │ │ +C3D02 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3D06 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3D08 Length 000B (11) │ │ │ │ +C3D0A Version 01 (1) │ │ │ │ +C3D0B UID Size 04 (4) │ │ │ │ +C3D0C UID 00000000 (0) │ │ │ │ +C3D10 GID Size 04 (4) │ │ │ │ +C3D11 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3D15 CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +C3D19 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3D1A Created OS 03 (3) 'Unix' │ │ │ │ +C3D1B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3D1C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3D1D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3D1F Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3D21 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3D25 CRC 2F723DFF (796016127) │ │ │ │ +C3D29 Compressed Size 00000409 (1033) │ │ │ │ +C3D2D Uncompressed Size 00000825 (2085) │ │ │ │ +C3D31 Filename Length 001C (28) │ │ │ │ +C3D33 Extra Length 0018 (24) │ │ │ │ +C3D35 Comment Length 0000 (0) │ │ │ │ +C3D37 Disk Start 0000 (0) │ │ │ │ +C3D39 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3D3B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3D3F Local Header Offset 00075BAC (482220) │ │ │ │ +C3D43 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3D43: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3D5F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3D61 Length 0005 (5) │ │ │ │ +C3D63 Flags 01 (1) 'Modification' │ │ │ │ +C3D64 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3D68 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3D6A Length 000B (11) │ │ │ │ +C3D6C Version 01 (1) │ │ │ │ +C3D6D UID Size 04 (4) │ │ │ │ +C3D6E UID 00000000 (0) │ │ │ │ +C3D72 GID Size 04 (4) │ │ │ │ +C3D73 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3D77 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +C3D7B Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3D7C Created OS 03 (3) 'Unix' │ │ │ │ +C3D7D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3D7E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3D7F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3D81 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3D83 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3D87 CRC AF61C4DB (2942420187) │ │ │ │ +C3D8B Compressed Size 000024C3 (9411) │ │ │ │ +C3D8F Uncompressed Size 0000B65D (46685) │ │ │ │ +C3D93 Filename Length 001F (31) │ │ │ │ +C3D95 Extra Length 0018 (24) │ │ │ │ +C3D97 Comment Length 0000 (0) │ │ │ │ +C3D99 Disk Start 0000 (0) │ │ │ │ +C3D9B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3D9D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3DA1 Local Header Offset 0007600B (483339) │ │ │ │ +C3DA5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3DA5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3DC4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3DC6 Length 0005 (5) │ │ │ │ +C3DC8 Flags 01 (1) 'Modification' │ │ │ │ +C3DC9 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3DCD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3DCF Length 000B (11) │ │ │ │ +C3DD1 Version 01 (1) │ │ │ │ +C3DD2 UID Size 04 (4) │ │ │ │ +C3DD3 UID 00000000 (0) │ │ │ │ +C3DD7 GID Size 04 (4) │ │ │ │ +C3DD8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3DDC CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +C3DE0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3DE1 Created OS 03 (3) 'Unix' │ │ │ │ +C3DE2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3DE3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3DE4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3DE6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3DE8 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3DEC CRC BDFBBF8D (3187392397) │ │ │ │ +C3DF0 Compressed Size 00000E7A (3706) │ │ │ │ +C3DF4 Uncompressed Size 000052DA (21210) │ │ │ │ +C3DF8 Filename Length 001F (31) │ │ │ │ +C3DFA Extra Length 0018 (24) │ │ │ │ +C3DFC Comment Length 0000 (0) │ │ │ │ +C3DFE Disk Start 0000 (0) │ │ │ │ +C3E00 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3E02 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3E06 Local Header Offset 00078527 (492839) │ │ │ │ +C3E0A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3E0A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3E29 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3E2B Length 0005 (5) │ │ │ │ +C3E2D Flags 01 (1) 'Modification' │ │ │ │ +C3E2E Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3E32 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3E34 Length 000B (11) │ │ │ │ +C3E36 Version 01 (1) │ │ │ │ +C3E37 UID Size 04 (4) │ │ │ │ +C3E38 UID 00000000 (0) │ │ │ │ +C3E3C GID Size 04 (4) │ │ │ │ +C3E3D GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3E41 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +C3E45 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3E46 Created OS 03 (3) 'Unix' │ │ │ │ +C3E47 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3E48 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3E49 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3E4B Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3E4D Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3E51 CRC 720DE003 (1913511939) │ │ │ │ +C3E55 Compressed Size 00000A45 (2629) │ │ │ │ +C3E59 Uncompressed Size 0000247A (9338) │ │ │ │ +C3E5D Filename Length 0013 (19) │ │ │ │ +C3E5F Extra Length 0018 (24) │ │ │ │ +C3E61 Comment Length 0000 (0) │ │ │ │ +C3E63 Disk Start 0000 (0) │ │ │ │ +C3E65 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3E67 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3E6B Local Header Offset 000793FA (496634) │ │ │ │ +C3E6F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3E6F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3E82 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3E84 Length 0005 (5) │ │ │ │ +C3E86 Flags 01 (1) 'Modification' │ │ │ │ +C3E87 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3E8B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3E8D Length 000B (11) │ │ │ │ +C3E8F Version 01 (1) │ │ │ │ +C3E90 UID Size 04 (4) │ │ │ │ +C3E91 UID 00000000 (0) │ │ │ │ +C3E95 GID Size 04 (4) │ │ │ │ +C3E96 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3E9A CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +C3E9E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3E9F Created OS 03 (3) 'Unix' │ │ │ │ +C3EA0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3EA1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3EA2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3EA4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3EA6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3EAA CRC 2F406ADE (792750814) │ │ │ │ +C3EAE Compressed Size 0000258D (9613) │ │ │ │ +C3EB2 Uncompressed Size 0000BAA4 (47780) │ │ │ │ +C3EB6 Filename Length 0019 (25) │ │ │ │ +C3EB8 Extra Length 0018 (24) │ │ │ │ +C3EBA Comment Length 0000 (0) │ │ │ │ +C3EBC Disk Start 0000 (0) │ │ │ │ +C3EBE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3EC0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3EC4 Local Header Offset 00079E8C (499340) │ │ │ │ +C3EC8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3EC8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3EE1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3EE3 Length 0005 (5) │ │ │ │ +C3EE5 Flags 01 (1) 'Modification' │ │ │ │ +C3EE6 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3EEA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3EEC Length 000B (11) │ │ │ │ +C3EEE Version 01 (1) │ │ │ │ +C3EEF UID Size 04 (4) │ │ │ │ +C3EF0 UID 00000000 (0) │ │ │ │ +C3EF4 GID Size 04 (4) │ │ │ │ +C3EF5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3EF9 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +C3EFD Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3EFE Created OS 03 (3) 'Unix' │ │ │ │ +C3EFF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3F00 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3F01 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3F03 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3F05 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3F09 CRC E061E754 (3764512596) │ │ │ │ +C3F0D Compressed Size 00000EFD (3837) │ │ │ │ +C3F11 Uncompressed Size 00003A2F (14895) │ │ │ │ +C3F15 Filename Length 0024 (36) │ │ │ │ +C3F17 Extra Length 0018 (24) │ │ │ │ +C3F19 Comment Length 0000 (0) │ │ │ │ +C3F1B Disk Start 0000 (0) │ │ │ │ +C3F1D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3F1F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3F23 Local Header Offset 0007C46C (509036) │ │ │ │ +C3F27 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3F27: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3F4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3F4D Length 0005 (5) │ │ │ │ +C3F4F Flags 01 (1) 'Modification' │ │ │ │ +C3F50 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3F54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3F56 Length 000B (11) │ │ │ │ +C3F58 Version 01 (1) │ │ │ │ +C3F59 UID Size 04 (4) │ │ │ │ +C3F5A UID 00000000 (0) │ │ │ │ +C3F5E GID Size 04 (4) │ │ │ │ +C3F5F GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3F63 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +C3F67 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3F68 Created OS 03 (3) 'Unix' │ │ │ │ +C3F69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3F6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3F6B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3F6D Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3F6F Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3F73 CRC EC4F7E41 (3964632641) │ │ │ │ +C3F77 Compressed Size 00001AE9 (6889) │ │ │ │ +C3F7B Uncompressed Size 00005F7F (24447) │ │ │ │ +C3F7F Filename Length 0017 (23) │ │ │ │ +C3F81 Extra Length 0018 (24) │ │ │ │ +C3F83 Comment Length 0000 (0) │ │ │ │ +C3F85 Disk Start 0000 (0) │ │ │ │ +C3F87 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3F89 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3F8D Local Header Offset 0007D3C7 (512967) │ │ │ │ +C3F91 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3F91: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C3FA8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C3FAA Length 0005 (5) │ │ │ │ +C3FAC Flags 01 (1) 'Modification' │ │ │ │ +C3FAD Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C3FB1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C3FB3 Length 000B (11) │ │ │ │ +C3FB5 Version 01 (1) │ │ │ │ +C3FB6 UID Size 04 (4) │ │ │ │ +C3FB7 UID 00000000 (0) │ │ │ │ +C3FBB GID Size 04 (4) │ │ │ │ +C3FBC GID 00000000 (0) │ │ │ │ + │ │ │ │ +C3FC0 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +C3FC4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C3FC5 Created OS 03 (3) 'Unix' │ │ │ │ +C3FC6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C3FC7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C3FC8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C3FCA Compression Method 0008 (8) 'Deflated' │ │ │ │ +C3FCC Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C3FD0 CRC 11E32AF1 (300100337) │ │ │ │ +C3FD4 Compressed Size 00000ED3 (3795) │ │ │ │ +C3FD8 Uncompressed Size 000038E2 (14562) │ │ │ │ +C3FDC Filename Length 0023 (35) │ │ │ │ +C3FDE Extra Length 0018 (24) │ │ │ │ +C3FE0 Comment Length 0000 (0) │ │ │ │ +C3FE2 Disk Start 0000 (0) │ │ │ │ +C3FE4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C3FE6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C3FEA Local Header Offset 0007EF01 (519937) │ │ │ │ +C3FEE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC3FEE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4011 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4013 Length 0005 (5) │ │ │ │ +C4015 Flags 01 (1) 'Modification' │ │ │ │ +C4016 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C401A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C401C Length 000B (11) │ │ │ │ +C401E Version 01 (1) │ │ │ │ +C401F UID Size 04 (4) │ │ │ │ +C4020 UID 00000000 (0) │ │ │ │ +C4024 GID Size 04 (4) │ │ │ │ +C4025 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4029 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +C402D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C402E Created OS 03 (3) 'Unix' │ │ │ │ +C402F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4030 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4031 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4033 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4035 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4039 CRC 2DB7929F (767005343) │ │ │ │ +C403D Compressed Size 00000113 (275) │ │ │ │ +C4041 Uncompressed Size 000001F3 (499) │ │ │ │ +C4045 Filename Length 001B (27) │ │ │ │ +C4047 Extra Length 0018 (24) │ │ │ │ +C4049 Comment Length 0000 (0) │ │ │ │ +C404B Disk Start 0000 (0) │ │ │ │ +C404D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C404F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4053 Local Header Offset 0007FE31 (523825) │ │ │ │ +C4057 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4057: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4072 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4074 Length 0005 (5) │ │ │ │ +C4076 Flags 01 (1) 'Modification' │ │ │ │ +C4077 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C407B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C407D Length 000B (11) │ │ │ │ +C407F Version 01 (1) │ │ │ │ +C4080 UID Size 04 (4) │ │ │ │ +C4081 UID 00000000 (0) │ │ │ │ +C4085 GID Size 04 (4) │ │ │ │ +C4086 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C408A CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +C408E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C408F Created OS 03 (3) 'Unix' │ │ │ │ +C4090 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4091 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4092 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4094 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4096 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C409A CRC CA00EBE0 (3389058016) │ │ │ │ +C409E Compressed Size 0000188D (6285) │ │ │ │ +C40A2 Uncompressed Size 00008FA8 (36776) │ │ │ │ +C40A6 Filename Length 001D (29) │ │ │ │ +C40A8 Extra Length 0018 (24) │ │ │ │ +C40AA Comment Length 0000 (0) │ │ │ │ +C40AC Disk Start 0000 (0) │ │ │ │ +C40AE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C40B0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C40B4 Local Header Offset 0007FF99 (524185) │ │ │ │ +C40B8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC40B8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C40D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C40D7 Length 0005 (5) │ │ │ │ +C40D9 Flags 01 (1) 'Modification' │ │ │ │ +C40DA Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C40DE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C40E0 Length 000B (11) │ │ │ │ +C40E2 Version 01 (1) │ │ │ │ +C40E3 UID Size 04 (4) │ │ │ │ +C40E4 UID 00000000 (0) │ │ │ │ +C40E8 GID Size 04 (4) │ │ │ │ +C40E9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C40ED CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +C40F1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C40F2 Created OS 03 (3) 'Unix' │ │ │ │ +C40F3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C40F4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C40F5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C40F7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C40F9 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C40FD CRC 13F8A234 (335061556) │ │ │ │ +C4101 Compressed Size 0000164C (5708) │ │ │ │ +C4105 Uncompressed Size 00003A9B (15003) │ │ │ │ +C4109 Filename Length 0015 (21) │ │ │ │ +C410B Extra Length 0018 (24) │ │ │ │ +C410D Comment Length 0000 (0) │ │ │ │ +C410F Disk Start 0000 (0) │ │ │ │ +C4111 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4113 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4117 Local Header Offset 0008187D (530557) │ │ │ │ +C411B Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC411B: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4130 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4132 Length 0005 (5) │ │ │ │ +C4134 Flags 01 (1) 'Modification' │ │ │ │ +C4135 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4139 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C413B Length 000B (11) │ │ │ │ +C413D Version 01 (1) │ │ │ │ +C413E UID Size 04 (4) │ │ │ │ +C413F UID 00000000 (0) │ │ │ │ +C4143 GID Size 04 (4) │ │ │ │ +C4144 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4148 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +C414C Created Zip Spec 3D (61) '6.1' │ │ │ │ +C414D Created OS 03 (3) 'Unix' │ │ │ │ +C414E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C414F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4150 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4152 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4154 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4158 CRC A6BC1ACF (2797345487) │ │ │ │ +C415C Compressed Size 000040C8 (16584) │ │ │ │ +C4160 Uncompressed Size 000133AC (78764) │ │ │ │ +C4164 Filename Length 0016 (22) │ │ │ │ +C4166 Extra Length 0018 (24) │ │ │ │ +C4168 Comment Length 0000 (0) │ │ │ │ +C416A Disk Start 0000 (0) │ │ │ │ +C416C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C416E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4172 Local Header Offset 00082F18 (536344) │ │ │ │ +C4176 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4176: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C418C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C418E Length 0005 (5) │ │ │ │ +C4190 Flags 01 (1) 'Modification' │ │ │ │ +C4191 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4195 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4197 Length 000B (11) │ │ │ │ +C4199 Version 01 (1) │ │ │ │ +C419A UID Size 04 (4) │ │ │ │ +C419B UID 00000000 (0) │ │ │ │ +C419F GID Size 04 (4) │ │ │ │ +C41A0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C41A4 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +C41A8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C41A9 Created OS 03 (3) 'Unix' │ │ │ │ +C41AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C41AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C41AC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C41AE Compression Method 0008 (8) 'Deflated' │ │ │ │ +C41B0 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C41B4 CRC 3D47632B (1028088619) │ │ │ │ +C41B8 Compressed Size 00003EB7 (16055) │ │ │ │ +C41BC Uncompressed Size 0001C78B (116619) │ │ │ │ +C41C0 Filename Length 0019 (25) │ │ │ │ +C41C2 Extra Length 0018 (24) │ │ │ │ +C41C4 Comment Length 0000 (0) │ │ │ │ +C41C6 Disk Start 0000 (0) │ │ │ │ +C41C8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C41CA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C41CE Local Header Offset 00087030 (553008) │ │ │ │ +C41D2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC41D2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C41EB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C41ED Length 0005 (5) │ │ │ │ +C41EF Flags 01 (1) 'Modification' │ │ │ │ +C41F0 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C41F4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C41F6 Length 000B (11) │ │ │ │ +C41F8 Version 01 (1) │ │ │ │ +C41F9 UID Size 04 (4) │ │ │ │ +C41FA UID 00000000 (0) │ │ │ │ +C41FE GID Size 04 (4) │ │ │ │ +C41FF GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4203 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +C4207 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4208 Created OS 03 (3) 'Unix' │ │ │ │ +C4209 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C420A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C420B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C420D Compression Method 0008 (8) 'Deflated' │ │ │ │ +C420F Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4213 CRC 339E52AB (866013867) │ │ │ │ +C4217 Compressed Size 000008A3 (2211) │ │ │ │ +C421B Uncompressed Size 000036CC (14028) │ │ │ │ +C421F Filename Length 0011 (17) │ │ │ │ +C4221 Extra Length 0018 (24) │ │ │ │ +C4223 Comment Length 0000 (0) │ │ │ │ +C4225 Disk Start 0000 (0) │ │ │ │ +C4227 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4229 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C422D Local Header Offset 0008AF3A (569146) │ │ │ │ +C4231 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4231: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4242 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4244 Length 0005 (5) │ │ │ │ +C4246 Flags 01 (1) 'Modification' │ │ │ │ +C4247 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C424B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C424D Length 000B (11) │ │ │ │ +C424F Version 01 (1) │ │ │ │ +C4250 UID Size 04 (4) │ │ │ │ +C4251 UID 00000000 (0) │ │ │ │ +C4255 GID Size 04 (4) │ │ │ │ +C4256 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C425A CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +C425E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C425F Created OS 03 (3) 'Unix' │ │ │ │ +C4260 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4261 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4262 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4264 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4266 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C426A CRC FFBD87B5 (4290611125) │ │ │ │ +C426E Compressed Size 000051A3 (20899) │ │ │ │ +C4272 Uncompressed Size 0001F99A (129434) │ │ │ │ +C4276 Filename Length 0015 (21) │ │ │ │ +C4278 Extra Length 0018 (24) │ │ │ │ +C427A Comment Length 0000 (0) │ │ │ │ +C427C Disk Start 0000 (0) │ │ │ │ +C427E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4280 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4284 Local Header Offset 0008B828 (571432) │ │ │ │ +C4288 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4288: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C429D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C429F Length 0005 (5) │ │ │ │ +C42A1 Flags 01 (1) 'Modification' │ │ │ │ +C42A2 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C42A6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C42A8 Length 000B (11) │ │ │ │ +C42AA Version 01 (1) │ │ │ │ +C42AB UID Size 04 (4) │ │ │ │ +C42AC UID 00000000 (0) │ │ │ │ +C42B0 GID Size 04 (4) │ │ │ │ +C42B1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C42B5 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +C42B9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C42BA Created OS 03 (3) 'Unix' │ │ │ │ +C42BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C42BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C42BD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C42BF Compression Method 0008 (8) 'Deflated' │ │ │ │ +C42C1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C42C5 CRC 1BAA9D20 (464166176) │ │ │ │ +C42C9 Compressed Size 00001C43 (7235) │ │ │ │ +C42CD Uncompressed Size 00008AC8 (35528) │ │ │ │ +C42D1 Filename Length 0019 (25) │ │ │ │ +C42D3 Extra Length 0018 (24) │ │ │ │ +C42D5 Comment Length 0000 (0) │ │ │ │ +C42D7 Disk Start 0000 (0) │ │ │ │ +C42D9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C42DB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C42DF Local Header Offset 00090A1A (592410) │ │ │ │ +C42E3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC42E3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C42FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C42FE Length 0005 (5) │ │ │ │ +C4300 Flags 01 (1) 'Modification' │ │ │ │ +C4301 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4305 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4307 Length 000B (11) │ │ │ │ +C4309 Version 01 (1) │ │ │ │ +C430A UID Size 04 (4) │ │ │ │ +C430B UID 00000000 (0) │ │ │ │ +C430F GID Size 04 (4) │ │ │ │ +C4310 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4314 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +C4318 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4319 Created OS 03 (3) 'Unix' │ │ │ │ +C431A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C431B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C431C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C431E Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4320 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4324 CRC FAE8837D (4209542013) │ │ │ │ +C4328 Compressed Size 00000D92 (3474) │ │ │ │ +C432C Uncompressed Size 00002EA4 (11940) │ │ │ │ +C4330 Filename Length 0018 (24) │ │ │ │ +C4332 Extra Length 0018 (24) │ │ │ │ +C4334 Comment Length 0000 (0) │ │ │ │ +C4336 Disk Start 0000 (0) │ │ │ │ +C4338 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C433A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C433E Local Header Offset 000926B0 (599728) │ │ │ │ +C4342 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4342: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C435A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C435C Length 0005 (5) │ │ │ │ +C435E Flags 01 (1) 'Modification' │ │ │ │ +C435F Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4363 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4365 Length 000B (11) │ │ │ │ +C4367 Version 01 (1) │ │ │ │ +C4368 UID Size 04 (4) │ │ │ │ +C4369 UID 00000000 (0) │ │ │ │ +C436D GID Size 04 (4) │ │ │ │ +C436E GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4372 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +C4376 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4377 Created OS 03 (3) 'Unix' │ │ │ │ +C4378 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4379 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C437A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C437C Compression Method 0008 (8) 'Deflated' │ │ │ │ +C437E Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4382 CRC 535B63FB (1398498299) │ │ │ │ +C4386 Compressed Size 000001DF (479) │ │ │ │ +C438A Uncompressed Size 00000323 (803) │ │ │ │ +C438E Filename Length 0011 (17) │ │ │ │ +C4390 Extra Length 0018 (24) │ │ │ │ +C4392 Comment Length 0000 (0) │ │ │ │ +C4394 Disk Start 0000 (0) │ │ │ │ +C4396 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4398 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C439C Local Header Offset 00093494 (603284) │ │ │ │ +C43A0 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC43A0: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C43B1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C43B3 Length 0005 (5) │ │ │ │ +C43B5 Flags 01 (1) 'Modification' │ │ │ │ +C43B6 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C43BA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C43BC Length 000B (11) │ │ │ │ +C43BE Version 01 (1) │ │ │ │ +C43BF UID Size 04 (4) │ │ │ │ +C43C0 UID 00000000 (0) │ │ │ │ +C43C4 GID Size 04 (4) │ │ │ │ +C43C5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C43C9 CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +C43CD Created Zip Spec 3D (61) '6.1' │ │ │ │ +C43CE Created OS 03 (3) 'Unix' │ │ │ │ +C43CF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C43D0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C43D1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C43D3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C43D5 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C43D9 CRC 32CAD6D8 (852154072) │ │ │ │ +C43DD Compressed Size 000006BE (1726) │ │ │ │ +C43E1 Uncompressed Size 0000141F (5151) │ │ │ │ +C43E5 Filename Length 0019 (25) │ │ │ │ +C43E7 Extra Length 0018 (24) │ │ │ │ +C43E9 Comment Length 0000 (0) │ │ │ │ +C43EB Disk Start 0000 (0) │ │ │ │ +C43ED Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C43EF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C43F3 Local Header Offset 000936BE (603838) │ │ │ │ +C43F7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC43F7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4410 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4412 Length 0005 (5) │ │ │ │ +C4414 Flags 01 (1) 'Modification' │ │ │ │ +C4415 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4419 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C441B Length 000B (11) │ │ │ │ +C441D Version 01 (1) │ │ │ │ +C441E UID Size 04 (4) │ │ │ │ +C441F UID 00000000 (0) │ │ │ │ +C4423 GID Size 04 (4) │ │ │ │ +C4424 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4428 CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +C442C Created Zip Spec 3D (61) '6.1' │ │ │ │ +C442D Created OS 03 (3) 'Unix' │ │ │ │ +C442E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C442F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4430 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4432 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4434 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4438 CRC 3082FD4D (813890893) │ │ │ │ +C443C Compressed Size 00001B8B (7051) │ │ │ │ +C4440 Uncompressed Size 00009F5F (40799) │ │ │ │ +C4444 Filename Length 0018 (24) │ │ │ │ +C4446 Extra Length 0018 (24) │ │ │ │ +C4448 Comment Length 0000 (0) │ │ │ │ +C444A Disk Start 0000 (0) │ │ │ │ +C444C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C444E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4452 Local Header Offset 00093DCF (605647) │ │ │ │ +C4456 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4456: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C446E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4470 Length 0005 (5) │ │ │ │ +C4472 Flags 01 (1) 'Modification' │ │ │ │ +C4473 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4477 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4479 Length 000B (11) │ │ │ │ +C447B Version 01 (1) │ │ │ │ +C447C UID Size 04 (4) │ │ │ │ +C447D UID 00000000 (0) │ │ │ │ +C4481 GID Size 04 (4) │ │ │ │ +C4482 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4486 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +C448A Created Zip Spec 3D (61) '6.1' │ │ │ │ +C448B Created OS 03 (3) 'Unix' │ │ │ │ +C448C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C448D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C448E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4490 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4492 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4496 CRC 1E04A61F (503621151) │ │ │ │ +C449A Compressed Size 00001700 (5888) │ │ │ │ +C449E Uncompressed Size 00008B12 (35602) │ │ │ │ +C44A2 Filename Length 0012 (18) │ │ │ │ +C44A4 Extra Length 0018 (24) │ │ │ │ +C44A6 Comment Length 0000 (0) │ │ │ │ +C44A8 Disk Start 0000 (0) │ │ │ │ +C44AA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C44AC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C44B0 Local Header Offset 000959AC (612780) │ │ │ │ +C44B4 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC44B4: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C44C6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C44C8 Length 0005 (5) │ │ │ │ +C44CA Flags 01 (1) 'Modification' │ │ │ │ +C44CB Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C44CF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C44D1 Length 000B (11) │ │ │ │ +C44D3 Version 01 (1) │ │ │ │ +C44D4 UID Size 04 (4) │ │ │ │ +C44D5 UID 00000000 (0) │ │ │ │ +C44D9 GID Size 04 (4) │ │ │ │ +C44DA GID 00000000 (0) │ │ │ │ + │ │ │ │ +C44DE CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +C44E2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C44E3 Created OS 03 (3) 'Unix' │ │ │ │ +C44E4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C44E5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C44E6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C44E8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C44EA Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C44EE CRC C9194D7F (3373878655) │ │ │ │ +C44F2 Compressed Size 00001E0B (7691) │ │ │ │ +C44F6 Uncompressed Size 00008823 (34851) │ │ │ │ +C44FA Filename Length 0016 (22) │ │ │ │ +C44FC Extra Length 0018 (24) │ │ │ │ +C44FE Comment Length 0000 (0) │ │ │ │ +C4500 Disk Start 0000 (0) │ │ │ │ +C4502 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4504 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4508 Local Header Offset 000970F8 (618744) │ │ │ │ +C450C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC450C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4522 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4524 Length 0005 (5) │ │ │ │ +C4526 Flags 01 (1) 'Modification' │ │ │ │ +C4527 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C452B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C452D Length 000B (11) │ │ │ │ +C452F Version 01 (1) │ │ │ │ +C4530 UID Size 04 (4) │ │ │ │ +C4531 UID 00000000 (0) │ │ │ │ +C4535 GID Size 04 (4) │ │ │ │ +C4536 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C453A CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +C453E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C453F Created OS 03 (3) 'Unix' │ │ │ │ +C4540 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4541 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4542 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4544 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4546 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C454A CRC FC5E70B2 (4234047666) │ │ │ │ +C454E Compressed Size 000029A9 (10665) │ │ │ │ +C4552 Uncompressed Size 0000D04F (53327) │ │ │ │ +C4556 Filename Length 001A (26) │ │ │ │ +C4558 Extra Length 0018 (24) │ │ │ │ +C455A Comment Length 0000 (0) │ │ │ │ +C455C Disk Start 0000 (0) │ │ │ │ +C455E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4560 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4564 Local Header Offset 00098F53 (626515) │ │ │ │ +C4568 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4568: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4582 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4584 Length 0005 (5) │ │ │ │ +C4586 Flags 01 (1) 'Modification' │ │ │ │ +C4587 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C458B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C458D Length 000B (11) │ │ │ │ +C458F Version 01 (1) │ │ │ │ +C4590 UID Size 04 (4) │ │ │ │ +C4591 UID 00000000 (0) │ │ │ │ +C4595 GID Size 04 (4) │ │ │ │ +C4596 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C459A CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +C459E Created Zip Spec 3D (61) '6.1' │ │ │ │ +C459F Created OS 03 (3) 'Unix' │ │ │ │ +C45A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C45A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C45A2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C45A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C45A6 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C45AA CRC 8529CAAD (2234108589) │ │ │ │ +C45AE Compressed Size 000009AB (2475) │ │ │ │ +C45B2 Uncompressed Size 00001DAC (7596) │ │ │ │ +C45B6 Filename Length 0018 (24) │ │ │ │ +C45B8 Extra Length 0018 (24) │ │ │ │ +C45BA Comment Length 0000 (0) │ │ │ │ +C45BC Disk Start 0000 (0) │ │ │ │ +C45BE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C45C0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C45C4 Local Header Offset 0009B950 (637264) │ │ │ │ +C45C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC45C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C45E0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C45E2 Length 0005 (5) │ │ │ │ +C45E4 Flags 01 (1) 'Modification' │ │ │ │ +C45E5 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C45E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C45EB Length 000B (11) │ │ │ │ +C45ED Version 01 (1) │ │ │ │ +C45EE UID Size 04 (4) │ │ │ │ +C45EF UID 00000000 (0) │ │ │ │ +C45F3 GID Size 04 (4) │ │ │ │ +C45F4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C45F8 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +C45FC Created Zip Spec 3D (61) '6.1' │ │ │ │ +C45FD Created OS 03 (3) 'Unix' │ │ │ │ +C45FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C45FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4600 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4602 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4604 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4608 CRC F0556E9A (4032130714) │ │ │ │ +C460C Compressed Size 000152EE (86766) │ │ │ │ +C4610 Uncompressed Size 000159F8 (88568) │ │ │ │ +C4614 Filename Length 001E (30) │ │ │ │ +C4616 Extra Length 0018 (24) │ │ │ │ +C4618 Comment Length 0000 (0) │ │ │ │ +C461A Disk Start 0000 (0) │ │ │ │ +C461C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C461E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4622 Local Header Offset 0009C34D (639821) │ │ │ │ +C4626 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4626: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4644 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4646 Length 0005 (5) │ │ │ │ +C4648 Flags 01 (1) 'Modification' │ │ │ │ +C4649 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C464D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C464F Length 000B (11) │ │ │ │ +C4651 Version 01 (1) │ │ │ │ +C4652 UID Size 04 (4) │ │ │ │ +C4653 UID 00000000 (0) │ │ │ │ +C4657 GID Size 04 (4) │ │ │ │ +C4658 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C465C CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +C4660 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4661 Created OS 03 (3) 'Unix' │ │ │ │ +C4662 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4663 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4664 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4666 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4668 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C466C CRC F5E2129F (4125233823) │ │ │ │ +C4670 Compressed Size 000016BC (5820) │ │ │ │ +C4674 Uncompressed Size 000016CD (5837) │ │ │ │ +C4678 Filename Length 0015 (21) │ │ │ │ +C467A Extra Length 0018 (24) │ │ │ │ +C467C Comment Length 0000 (0) │ │ │ │ +C467E Disk Start 0000 (0) │ │ │ │ +C4680 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4682 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4686 Local Header Offset 000B1693 (726675) │ │ │ │ +C468A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC468A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C469F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C46A1 Length 0005 (5) │ │ │ │ +C46A3 Flags 01 (1) 'Modification' │ │ │ │ +C46A4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C46A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C46AA Length 000B (11) │ │ │ │ +C46AC Version 01 (1) │ │ │ │ +C46AD UID Size 04 (4) │ │ │ │ +C46AE UID 00000000 (0) │ │ │ │ +C46B2 GID Size 04 (4) │ │ │ │ +C46B3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C46B7 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +C46BB Created Zip Spec 3D (61) '6.1' │ │ │ │ +C46BC Created OS 03 (3) 'Unix' │ │ │ │ +C46BD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C46BE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C46BF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C46C1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C46C3 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C46C7 CRC F5E2129F (4125233823) │ │ │ │ +C46CB Compressed Size 000016BC (5820) │ │ │ │ +C46CF Uncompressed Size 000016CD (5837) │ │ │ │ +C46D3 Filename Length 001C (28) │ │ │ │ +C46D5 Extra Length 0018 (24) │ │ │ │ +C46D7 Comment Length 0000 (0) │ │ │ │ +C46D9 Disk Start 0000 (0) │ │ │ │ +C46DB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C46DD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C46E1 Local Header Offset 000B2D9E (732574) │ │ │ │ +C46E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC46E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4701 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4703 Length 0005 (5) │ │ │ │ +C4705 Flags 01 (1) 'Modification' │ │ │ │ +C4706 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C470A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C470C Length 000B (11) │ │ │ │ +C470E Version 01 (1) │ │ │ │ +C470F UID Size 04 (4) │ │ │ │ +C4710 UID 00000000 (0) │ │ │ │ +C4714 GID Size 04 (4) │ │ │ │ +C4715 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4719 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +C471D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C471E Created OS 03 (3) 'Unix' │ │ │ │ +C471F Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C4720 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4721 General Purpose Flag 0000 (0) │ │ │ │ +C4723 Compression Method 0000 (0) 'Stored' │ │ │ │ +C4725 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4729 CRC FC95F24B (4237685323) │ │ │ │ +C472D Compressed Size 00001B84 (7044) │ │ │ │ +C4731 Uncompressed Size 00001B84 (7044) │ │ │ │ +C4735 Filename Length 0016 (22) │ │ │ │ +C4737 Extra Length 0018 (24) │ │ │ │ +C4739 Comment Length 0000 (0) │ │ │ │ +C473B Disk Start 0000 (0) │ │ │ │ +C473D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C473F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4743 Local Header Offset 000B44B0 (738480) │ │ │ │ +C4747 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4747: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C475D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C475F Length 0005 (5) │ │ │ │ +C4761 Flags 01 (1) 'Modification' │ │ │ │ +C4762 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4766 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4768 Length 000B (11) │ │ │ │ +C476A Version 01 (1) │ │ │ │ +C476B UID Size 04 (4) │ │ │ │ +C476C UID 00000000 (0) │ │ │ │ +C4770 GID Size 04 (4) │ │ │ │ +C4771 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4775 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +C4779 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C477A Created OS 03 (3) 'Unix' │ │ │ │ +C477B Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C477C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C477D General Purpose Flag 0000 (0) │ │ │ │ +C477F Compression Method 0000 (0) 'Stored' │ │ │ │ +C4781 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4785 CRC D0D71F86 (3503759238) │ │ │ │ +C4789 Compressed Size 00000B7B (2939) │ │ │ │ +C478D Uncompressed Size 00000B7B (2939) │ │ │ │ +C4791 Filename Length 0016 (22) │ │ │ │ +C4793 Extra Length 0018 (24) │ │ │ │ +C4795 Comment Length 0000 (0) │ │ │ │ +C4797 Disk Start 0000 (0) │ │ │ │ +C4799 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C479B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C479F Local Header Offset 000B6084 (745604) │ │ │ │ +C47A3 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC47A3: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C47B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C47BB Length 0005 (5) │ │ │ │ +C47BD Flags 01 (1) 'Modification' │ │ │ │ +C47BE Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C47C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C47C4 Length 000B (11) │ │ │ │ +C47C6 Version 01 (1) │ │ │ │ +C47C7 UID Size 04 (4) │ │ │ │ +C47C8 UID 00000000 (0) │ │ │ │ +C47CC GID Size 04 (4) │ │ │ │ +C47CD GID 00000000 (0) │ │ │ │ + │ │ │ │ +C47D1 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +C47D5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C47D6 Created OS 03 (3) 'Unix' │ │ │ │ +C47D7 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C47D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C47D9 General Purpose Flag 0000 (0) │ │ │ │ +C47DB Compression Method 0000 (0) 'Stored' │ │ │ │ +C47DD Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C47E1 CRC FFF9C4D2 (4294558930) │ │ │ │ +C47E5 Compressed Size 0000138F (5007) │ │ │ │ +C47E9 Uncompressed Size 0000138F (5007) │ │ │ │ +C47ED Filename Length 0016 (22) │ │ │ │ +C47EF Extra Length 0018 (24) │ │ │ │ +C47F1 Comment Length 0000 (0) │ │ │ │ +C47F3 Disk Start 0000 (0) │ │ │ │ +C47F5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C47F7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C47FB Local Header Offset 000B6C4F (748623) │ │ │ │ +C47FF Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC47FF: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4815 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4817 Length 0005 (5) │ │ │ │ +C4819 Flags 01 (1) 'Modification' │ │ │ │ +C481A Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C481E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4820 Length 000B (11) │ │ │ │ +C4822 Version 01 (1) │ │ │ │ +C4823 UID Size 04 (4) │ │ │ │ +C4824 UID 00000000 (0) │ │ │ │ +C4828 GID Size 04 (4) │ │ │ │ +C4829 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C482D CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +C4831 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4832 Created OS 03 (3) 'Unix' │ │ │ │ +C4833 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C4834 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4835 General Purpose Flag 0000 (0) │ │ │ │ +C4837 Compression Method 0000 (0) 'Stored' │ │ │ │ +C4839 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C483D CRC A1037E8E (2701360782) │ │ │ │ +C4841 Compressed Size 0000145E (5214) │ │ │ │ +C4845 Uncompressed Size 0000145E (5214) │ │ │ │ +C4849 Filename Length 0016 (22) │ │ │ │ +C484B Extra Length 0018 (24) │ │ │ │ +C484D Comment Length 0000 (0) │ │ │ │ +C484F Disk Start 0000 (0) │ │ │ │ +C4851 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4853 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4857 Local Header Offset 000B802E (753710) │ │ │ │ +C485B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC485B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4871 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4873 Length 0005 (5) │ │ │ │ +C4875 Flags 01 (1) 'Modification' │ │ │ │ +C4876 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C487A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C487C Length 000B (11) │ │ │ │ +C487E Version 01 (1) │ │ │ │ +C487F UID Size 04 (4) │ │ │ │ +C4880 UID 00000000 (0) │ │ │ │ +C4884 GID Size 04 (4) │ │ │ │ +C4885 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4889 CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ +C488D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C488E Created OS 03 (3) 'Unix' │ │ │ │ +C488F Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C4890 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4891 General Purpose Flag 0000 (0) │ │ │ │ +C4893 Compression Method 0000 (0) 'Stored' │ │ │ │ +C4895 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4899 CRC 5E9E64F1 (1587438833) │ │ │ │ +C489D Compressed Size 000008EC (2284) │ │ │ │ +C48A1 Uncompressed Size 000008EC (2284) │ │ │ │ +C48A5 Filename Length 0016 (22) │ │ │ │ +C48A7 Extra Length 0018 (24) │ │ │ │ +C48A9 Comment Length 0000 (0) │ │ │ │ +C48AB Disk Start 0000 (0) │ │ │ │ +C48AD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C48AF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C48B3 Local Header Offset 000B94DC (759004) │ │ │ │ +C48B7 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC48B7: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C48CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C48CF Length 0005 (5) │ │ │ │ +C48D1 Flags 01 (1) 'Modification' │ │ │ │ +C48D2 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C48D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C48D8 Length 000B (11) │ │ │ │ +C48DA Version 01 (1) │ │ │ │ +C48DB UID Size 04 (4) │ │ │ │ +C48DC UID 00000000 (0) │ │ │ │ +C48E0 GID Size 04 (4) │ │ │ │ +C48E1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C48E5 CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ +C48E9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C48EA Created OS 03 (3) 'Unix' │ │ │ │ +C48EB Extract Zip Spec 0A (10) '1.0' │ │ │ │ +C48EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C48ED General Purpose Flag 0000 (0) │ │ │ │ +C48EF Compression Method 0000 (0) 'Stored' │ │ │ │ +C48F1 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C48F5 CRC 42E340AB (1122189483) │ │ │ │ +C48F9 Compressed Size 00001F2E (7982) │ │ │ │ +C48FD Uncompressed Size 00001F2E (7982) │ │ │ │ +C4901 Filename Length 001E (30) │ │ │ │ +C4903 Extra Length 0018 (24) │ │ │ │ +C4905 Comment Length 0000 (0) │ │ │ │ +C4907 Disk Start 0000 (0) │ │ │ │ +C4909 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C490B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C490F Local Header Offset 000B9E18 (761368) │ │ │ │ +C4913 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4913: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4931 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4933 Length 0005 (5) │ │ │ │ +C4935 Flags 01 (1) 'Modification' │ │ │ │ +C4936 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C493A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C493C Length 000B (11) │ │ │ │ +C493E Version 01 (1) │ │ │ │ +C493F UID Size 04 (4) │ │ │ │ +C4940 UID 00000000 (0) │ │ │ │ +C4944 GID Size 04 (4) │ │ │ │ +C4945 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4949 CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ +C494D Created Zip Spec 3D (61) '6.1' │ │ │ │ +C494E Created OS 03 (3) 'Unix' │ │ │ │ +C494F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4950 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4951 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4953 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4955 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4959 CRC 28F7826D (687309421) │ │ │ │ +C495D Compressed Size 00003D80 (15744) │ │ │ │ +C4961 Uncompressed Size 000166B0 (91824) │ │ │ │ +C4965 Filename Length 001A (26) │ │ │ │ +C4967 Extra Length 0018 (24) │ │ │ │ +C4969 Comment Length 0000 (0) │ │ │ │ +C496B Disk Start 0000 (0) │ │ │ │ +C496D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C496F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4973 Local Header Offset 000BBD9E (769438) │ │ │ │ +C4977 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4977: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4991 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4993 Length 0005 (5) │ │ │ │ +C4995 Flags 01 (1) 'Modification' │ │ │ │ +C4996 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C499A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C499C Length 000B (11) │ │ │ │ +C499E Version 01 (1) │ │ │ │ +C499F UID Size 04 (4) │ │ │ │ +C49A0 UID 00000000 (0) │ │ │ │ +C49A4 GID Size 04 (4) │ │ │ │ +C49A5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C49A9 CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ +C49AD Created Zip Spec 3D (61) '6.1' │ │ │ │ +C49AE Created OS 03 (3) 'Unix' │ │ │ │ +C49AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C49B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C49B1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C49B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C49B5 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C49B9 CRC B0D0A5F7 (2966463991) │ │ │ │ +C49BD Compressed Size 000029D0 (10704) │ │ │ │ +C49C1 Uncompressed Size 0000BB3A (47930) │ │ │ │ +C49C5 Filename Length 0018 (24) │ │ │ │ +C49C7 Extra Length 0018 (24) │ │ │ │ +C49C9 Comment Length 0000 (0) │ │ │ │ +C49CB Disk Start 0000 (0) │ │ │ │ +C49CD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C49CF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C49D3 Local Header Offset 000BFB72 (785266) │ │ │ │ +C49D7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC49D7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C49EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C49F1 Length 0005 (5) │ │ │ │ +C49F3 Flags 01 (1) 'Modification' │ │ │ │ +C49F4 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C49F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C49FA Length 000B (11) │ │ │ │ +C49FC Version 01 (1) │ │ │ │ +C49FD UID Size 04 (4) │ │ │ │ +C49FE UID 00000000 (0) │ │ │ │ +C4A02 GID Size 04 (4) │ │ │ │ +C4A03 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4A07 CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ +C4A0B Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4A0C Created OS 03 (3) 'Unix' │ │ │ │ +C4A0D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4A0E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4A0F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4A11 Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4A13 Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4A17 CRC DCB3B516 (3702764822) │ │ │ │ +C4A1B Compressed Size 000000AE (174) │ │ │ │ +C4A1F Uncompressed Size 000000FC (252) │ │ │ │ +C4A23 Filename Length 0016 (22) │ │ │ │ +C4A25 Extra Length 0018 (24) │ │ │ │ +C4A27 Comment Length 0000 (0) │ │ │ │ +C4A29 Disk Start 0000 (0) │ │ │ │ +C4A2B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4A2D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4A31 Local Header Offset 000C2594 (796052) │ │ │ │ +C4A35 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4A35: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4A4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4A4D Length 0005 (5) │ │ │ │ +C4A4F Flags 01 (1) 'Modification' │ │ │ │ +C4A50 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4A54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4A56 Length 000B (11) │ │ │ │ +C4A58 Version 01 (1) │ │ │ │ +C4A59 UID Size 04 (4) │ │ │ │ +C4A5A UID 00000000 (0) │ │ │ │ +C4A5E GID Size 04 (4) │ │ │ │ +C4A5F GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4A63 CENTRAL HEADER #97 02014B50 (33639248) │ │ │ │ +C4A67 Created Zip Spec 3D (61) '6.1' │ │ │ │ +C4A68 Created OS 03 (3) 'Unix' │ │ │ │ +C4A69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +C4A6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +C4A6B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +C4A6D Compression Method 0008 (8) 'Deflated' │ │ │ │ +C4A6F Modification Time 5C9521B3 (1553277363) 'Tue Apr 21 04:13:38 2026' │ │ │ │ +C4A73 CRC 58439733 (1480824627) │ │ │ │ +C4A77 Compressed Size 00000077 (119) │ │ │ │ +C4A7B Uncompressed Size 000000A2 (162) │ │ │ │ +C4A7F Filename Length 002D (45) │ │ │ │ +C4A81 Extra Length 0018 (24) │ │ │ │ +C4A83 Comment Length 0000 (0) │ │ │ │ +C4A85 Disk Start 0000 (0) │ │ │ │ +C4A87 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +C4A89 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +C4A8D Local Header Offset 000C2692 (796306) │ │ │ │ +C4A91 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC4A91: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +C4ABE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +C4AC0 Length 0005 (5) │ │ │ │ +C4AC2 Flags 01 (1) 'Modification' │ │ │ │ +C4AC3 Modification Time 69E6F973 (1776744819) 'Tue Apr 21 04:13:39 2026' │ │ │ │ +C4AC7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +C4AC9 Length 000B (11) │ │ │ │ +C4ACB Version 01 (1) │ │ │ │ +C4ACC UID Size 04 (4) │ │ │ │ +C4ACD UID 00000000 (0) │ │ │ │ +C4AD1 GID Size 04 (4) │ │ │ │ +C4AD2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +C4AD6 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +C4ADA Number of this disk 0000 (0) │ │ │ │ +C4ADC Central Dir Disk no 0000 (0) │ │ │ │ +C4ADE Entries in this disk 0061 (97) │ │ │ │ +C4AE0 Total Entries 0061 (97) │ │ │ │ +C4AE2 Size of Central Dir 00002366 (9062) │ │ │ │ +C4AE6 Offset to Central Dir 000C2770 (796528) │ │ │ │ +C4AEA Comment Length 0000 (0) │ │ │ │ # │ │ │ │ # Warning Count: 194 │ │ │ │ # │ │ │ │ # Done │ │ │ ├── filetype from file(1) │ │ │ │ @@ -1 +1 @@ │ │ │ │ -Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Apr 15 2026 20:07:06, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Apr 21 2026 04:13:38, 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 of 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 of as │ │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ │ according to the type language defined above.

Built-in typeCan be thought of as 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, they start with an uppercase letter. These variables are 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 arities.

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

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 are 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 │ │ │ │ over #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 functions 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 dirty │ │ │ │ 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 looks as follows:

      #{id => ch3,
      │ │ │ │ -  start => {ch3, start_link, []},
      │ │ │ │ +example looks 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}<br />* Collect Buttons │ │ │ │ check_code --> locked : Incorrect code │ │ │ │ check_code --> open : Correct code<br />* do_unlock()<br />* Clear Buttons<br />* Set state_timeout 10 s │ │ │ │ │ │ │ │ open --> open : {button, Digit} │ │ │ │ open --> locked : state_timeout<br />* 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 stores 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/3:

      {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/3:

      {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 and 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 following 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 following 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 special 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. 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 handled by the following code:

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

          In the example, system messages are handled 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}.
          │ │ │ │ +system_get_state(Chs) ->
          │ │ │ │ +    {ok, 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, Dbg, ...).
          │ │ │ │ +    Dbg = sys:debug_options([]),
          │ │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
          │ │ │ │ +    loop(Parent, Module, Dbg, ...).
          │ │ │ │  
          │ │ │ │ -...

          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 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 can only 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 give an idea about 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 │ │ │ │

          │ │ │ │

          Atoms are another data type in Erlang. Atoms start with a lowercase 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 are converted to centimeters and back again, │ │ │ │ +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 are converted to centimeters and back again, │ │ │ │ yielding the original value. This also shows that 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 necessary 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 element of the list from the rest of the list, | is │ │ │ │ -used. First has the value 1 and TheRest has 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 element of the list from the rest of the list, | is │ │ │ │ +used. First has the value 1 and TheRest has 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 │ │ │ │ -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 has not already been 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) value to the 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) value to the 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 by default they belong 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 ends 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 a function that doubles the value of a number is defined and assigned 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, []) ->
          │ │ │ │ +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 library module lists. foreach takes │ │ │ │ +map(Fun, [First|Rest]) -> │ │ │ │ + [Fun(First)|map(Fun,Rest)]; │ │ │ │ +map(Fun, []) -> │ │ │ │ + [].

          These two functions are provided in the standard library 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 with a fun to add 3 to every element of a list:

          88> Add_3 = fun(X) -> X + 3 end.
          │ │ │ │ +used with 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 │ │ │ │ fun Function/Arity (remember that Arity = number of arguments). That is │ │ │ │ why fun convert_to_c/1 can be used in the call above. 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/secure_coding.xhtml │ │ │ │ @@ -279,108 +279,108 @@ │ │ │ │ left to the supervision structure, and all unexpected conditions should be │ │ │ │ considered errors. In brief, this is because encountering something unexpected │ │ │ │ means that we have left the known and tested path, and continuing greatly │ │ │ │ increases the risk for bugs and security issues.

          Erlang code should be written as restrictively as possible, to provoke errors │ │ │ │ whenever anything unexpected happens. The idea is to make the third error │ │ │ │ category, program bugs, visible as a crash instead of silently continuing.

          Rule priority: High

          Related CWEs and OWASP risks: CWE-252, CWE-253, CWE-391, CWE-392, │ │ │ │ CWE-394, CWE-396, A10:2025

          %% DO
          │ │ │ │ -case operation(A, B) of
          │ │ │ │ +case operation(A, B) of
          │ │ │ │      true -> C;
          │ │ │ │      false -> D
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -case operation(A, B) of
          │ │ │ │ +case operation(A, B) of
          │ │ │ │      true -> C;
          │ │ │ │      %% What if operation/2 is extended to also return 'maybe', or someone
          │ │ │ │      %% misspells 'true' as 'tru'?
          │ │ │ │      _ -> D
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %% DO
          │ │ │ │ -ok = file:write(Fd, Data)
          │ │ │ │ +ok = file:write(Fd, Data)
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -_ = file:write(Fd, Data)
          │ │ │ │ +_ = file:write(Fd, Data)
          │ │ │ │  
          │ │ │ │  %% DO
          │ │ │ │ -foo([First | Rest]) ->
          │ │ │ │ -    [bar(First) | foo(Rest)];
          │ │ │ │ -foo([]) ->
          │ │ │ │ -    [].
          │ │ │ │ +foo([First | Rest]) ->
          │ │ │ │ +    [bar(First) | foo(Rest)];
          │ │ │ │ +foo([]) ->
          │ │ │ │ +    [].
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -foo([First | Rest]) ->
          │ │ │ │ -    [bar(First) | foo(Rest)];
          │ │ │ │ -foo(_) ->
          │ │ │ │ -    [].
          │ │ │ │ +foo([First | Rest]) ->
          │ │ │ │ +    [bar(First) | foo(Rest)];
          │ │ │ │ +foo(_) ->
          │ │ │ │ +    [].
          │ │ │ │  
          │ │ │ │  %% DO
          │ │ │ │ -input_to_atom(<<"foo">>) -> foo;
          │ │ │ │ -input_to_atom(<<"bar">>) -> bar;
          │ │ │ │ -input_to_atom(<<"quux">>) -> quux.
          │ │ │ │ +input_to_atom(<<"foo">>) -> foo;
          │ │ │ │ +input_to_atom(<<"bar">>) -> bar;
          │ │ │ │ +input_to_atom(<<"quux">>) -> quux.
          │ │ │ │  
          │ │ │ │  %% DO NOT, when set of possible atoms is known beforehand
          │ │ │ │ -input_to_atom(Text) -> binary_to_existing_atom(Text).
          │ │ │ │ +input_to_atom(Text) -> binary_to_existing_atom(Text).
          │ │ │ │  
          │ │ │ │  %% DO
          │ │ │ │ -try operation(A, B) of
          │ │ │ │ -    {ok, X} -> something(X)
          │ │ │ │ +try operation(A, B) of
          │ │ │ │ +    {ok, X} -> something(X)
          │ │ │ │  catch
          │ │ │ │      error:specific_error -> error
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -try operation(A, B) of
          │ │ │ │ -    {ok, X} -> something(X)
          │ │ │ │ +try operation(A, B) of
          │ │ │ │ +    {ok, X} -> something(X)
          │ │ │ │  catch
          │ │ │ │      error:_ -> error
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %% PREFER
          │ │ │ │ -case my_filter(List0, unchanged) of
          │ │ │ │ +case my_filter(List0, unchanged) of
          │ │ │ │      unchanged -> List0;
          │ │ │ │ -    {changed, List} -> List
          │ │ │ │ +    {changed, List} -> List
          │ │ │ │  end
          │ │ │ │  
          │ │ │ │  %% AVOID
          │ │ │ │ -case my_filter(List0, unchanged) of
          │ │ │ │ +case my_filter(List0, unchanged) of
          │ │ │ │      unchanged -> List0;
          │ │ │ │      %% What if a misspelled atom like 'uchanged' is returned?
          │ │ │ │      List -> List
          │ │ │ │  end
          │ │ │ │  
          │ │ │ │  %% PREFER
          │ │ │ │ -[op(L) || #my_record{}=L <:- ListOfMyRecord]
          │ │ │ │ +[op(L) || #my_record{}=L <:- ListOfMyRecord]
          │ │ │ │  
          │ │ │ │  %% AVOID, this silently filters out entries that do not match #my_record{}
          │ │ │ │ -[op(L) || #my_record{}=L <- ListOfMyRecord]

          STL-002 - Avoid Boolean Blindness

          Whenever boolean values have a context, prefer using more descriptive atoms to │ │ │ │ +[op(L) || #my_record{}=L <- ListOfMyRecord]

          STL-002 - Avoid Boolean Blindness

          Whenever boolean values have a context, prefer using more descriptive atoms to │ │ │ │ express the boolean value, for example initialized/uninitialized or │ │ │ │ changed/unchanged. This makes it easier to distinguish between different │ │ │ │ boolean variables when many of them are used together, especially when matching │ │ │ │ in function heads and the like.

          Rule priority: Recommendation

          Related CWEs and OWASP risks: CWE-628

          %% DO
          │ │ │ │ -case my_filter(List0, unchanged) of
          │ │ │ │ +case my_filter(List0, unchanged) of
          │ │ │ │      unchanged -> List0;
          │ │ │ │ -    {changed, List} -> List
          │ │ │ │ +    {changed, List} -> List
          │ │ │ │  end
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -case my_filter(List0, false) of
          │ │ │ │ +case my_filter(List0, false) of
          │ │ │ │      false -> List0;
          │ │ │ │ -    {true, List} -> List
          │ │ │ │ +    {true, List} -> List
          │ │ │ │  end

          STL-003 - Use Uppercase Names for Macros

          Macros are distinguished by a ? prefix, so an accidental omission of the │ │ │ │ prefix leaves the name there instead of applying the macro. For example, │ │ │ │ function_call(?my_macro, SomeArg) becomes function_call(my_macro, SomeArg) │ │ │ │ which is syntactically valid, hiding the error.

          Static analysis tools can often find these issues, but a quicker way to find │ │ │ │ them is to adopt the convention that all macros should be upper-case. Missing a │ │ │ │ ? will in most cases then lead to an unbound variable error or similar.

          Rule priority: Recommendation

          %% DO
          │ │ │ │ --define(MY_MACRO, 65535).
          │ │ │ │ +-define(MY_MACRO, 65535).
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ --define(my_macro, 65535).

          │ │ │ │ +-define(my_macro, 65535).

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deployment │ │ │ │

          │ │ │ │

          DEP-001 - Do Not Expose Default Erlang Distribution on Untrusted Networks

          The builtin Erlang distribution makes it possible to easily and transparently │ │ │ │ communicate between Erlang nodes. By default, communication is performed over │ │ │ │ @@ -473,27 +473,27 @@ │ │ │ │ pervasive error handling throughout the code at large is drastically reduced, │ │ │ │ and with it a large source of bugs and security issues.

          Rule priority: Medium

          Related CWEs and OWASP risks: CWE-389, CWE-544, CWE-653, A10:2025

          DSG-002 - Prefer Letting the User Decide What Warrants an Exception

          Prefer to design your interfaces so that the user decides whether an error is │ │ │ │ exceptional or not, by following the {ok, Result} | {error, Reason} │ │ │ │ convention. Generally speaking the user of an interface has more context than │ │ │ │ the one implementing it, and giving them the freedom to choose through pattern │ │ │ │ matching tends to result in clearer code as the handling of raised exceptions │ │ │ │ is more difficult to follow.

          Rule priority: Recommendation

          Related CWEs and OWASP risks: CWE-389, A10:2025

          %% PREFER
          │ │ │ │ -{ok, C} = some_function(A, B)
          │ │ │ │ +{ok, C} = some_function(A, B)
          │ │ │ │  
          │ │ │ │  %% PREFER
          │ │ │ │ -case some_function(A, B) of
          │ │ │ │ -    {ok, C} ->
          │ │ │ │ +case some_function(A, B) of
          │ │ │ │ +    {ok, C} ->
          │ │ │ │          %% Happy path
          │ │ │ │          ...;
          │ │ │ │ -    {error, Error} ->
          │ │ │ │ +    {error, Error} ->
          │ │ │ │          %% Handle it
          │ │ │ │  end
          │ │ │ │  
          │ │ │ │  %% AVOID
          │ │ │ │ -try some_function(A, B) of
          │ │ │ │ +try some_function(A, B) of
          │ │ │ │      C -> 
          │ │ │ │          %% Happy path
          │ │ │ │          ...
          │ │ │ │  catch
          │ │ │ │      error:_ ->
          │ │ │ │          %% Handle it
          │ │ │ │  end

          DSG-003 - Do Not Abuse Atoms

          Atoms are designed to provide an easy way to create named constants in code. │ │ │ │ @@ -515,23 +515,23 @@ │ │ │ │ DSG-011) unless the API also provides some way of preventing creation of │ │ │ │ atoms. For example, binary_to_term/2 with the safe option will prevent │ │ │ │ new atoms from being created. However, note that even if the safe option is │ │ │ │ used and the data originates from an untrusted source, it still has to be │ │ │ │ validated and sanitized, since it can still be harmful to the Erlang │ │ │ │ application in other ways.

          In general, it is best to avoid using such functions altogether on untrusted │ │ │ │ data, even with the safe option.

          Rule priority: High

          Related CWEs and OWASP risks: CWE-770, API10:2023

          %% DO, AND PREFER (see STL-001)
          │ │ │ │ -input_to_atom(<<"foo">>) -> foo;
          │ │ │ │ -input_to_atom(<<"bar">>) -> bar;
          │ │ │ │ -input_to_atom(<<"quux">>) -> quux.
          │ │ │ │ +input_to_atom(<<"foo">>) -> foo;
          │ │ │ │ +input_to_atom(<<"bar">>) -> bar;
          │ │ │ │ +input_to_atom(<<"quux">>) -> quux.
          │ │ │ │  
          │ │ │ │  %% DO
          │ │ │ │ -input_to_atom(Text) -> binary_to_existing_atom(Text).
          │ │ │ │ +input_to_atom(Text) -> binary_to_existing_atom(Text).
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -input_to_atom(Text) -> binary_to_atom(Text).

          DSG-004 - Do Not Use Undocumented Functionality

          Undocumented functions or functionality must never be used. This includes │ │ │ │ +input_to_atom(Text) -> binary_to_atom(Text).

          DSG-004 - Do Not Use Undocumented Functionality

          Undocumented functions or functionality must never be used. This includes │ │ │ │ undocumented arguments to documented functions and undocumented system │ │ │ │ services. Using such functionality poses a serious security risk. These │ │ │ │ functions and features are intended strictly for internal use within Erlang/OTP │ │ │ │ and are not supported for external use.

          Merely passing the wrong arguments to these functions can cause the system to │ │ │ │ behave in unexpected ways from that point on, and their behavior may change or │ │ │ │ they may be removed without prior notice.

          Rule priority: Critical

          Related CWEs and OWASP risks: CWE-242, CWE-477, CWE-676

          DSG-005 - Do Not Use Deprecated Functionality

          When functionality is deprecated in Erlang/OTP, the documentation will │ │ │ │ typically point to other or new functionality to use instead. The deprecation │ │ │ │ @@ -572,28 +572,28 @@ │ │ │ │ them through their name instead of their process or table identifier. This │ │ │ │ should be used with care, as a process or table may terminate at any time. │ │ │ │ For example, if two messages are sent to a process through a registered name, │ │ │ │ the second message may arrive to a newly restarted process that has not seen │ │ │ │ the first, which may be significant (CWE-386).

          To prevent these issues, either redesign your interface so that multiple │ │ │ │ messages or lookups are not necessary, or look up the identifier from the │ │ │ │ registered name and use the identifier instead.

          Rule priority: Recommendation

          Related CWEs and OWASP risks: CWE-386

          %% DO
          │ │ │ │ -Pid = whereis(registered_process),
          │ │ │ │ +Pid = whereis(registered_process),
          │ │ │ │  Pid ! hello,
          │ │ │ │  Pid ! world.
          │ │ │ │  
          │ │ │ │ -Tid = ets:whereis(registered_table),
          │ │ │ │ -A = ets:lookup(Tid, KeyA),
          │ │ │ │ -B = ets:lookup(Tid, KeyB).
          │ │ │ │ +Tid = ets:whereis(registered_table),
          │ │ │ │ +A = ets:lookup(Tid, KeyA),
          │ │ │ │ +B = ets:lookup(Tid, KeyB).
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │  registered_process ! hello,
          │ │ │ │  registered_process ! world.
          │ │ │ │  
          │ │ │ │ -A = ets:lookup(registered_table, KeyA),
          │ │ │ │ -B = ets:lookup(registered_table, KeyB).

          DSG-011 - Only Deserialize Trusted Data

          Erlang/OTP provides various functionality that serializes and deserializes │ │ │ │ +A = ets:lookup(registered_table, KeyA), │ │ │ │ +B = ets:lookup(registered_table, KeyB).

          DSG-011 - Only Deserialize Trusted Data

          Erlang/OTP provides various functionality that serializes and deserializes │ │ │ │ general Erlang terms. Such functionality is intended to be used in a trusted │ │ │ │ environment and is not suitable for communication with untrusted entities. For │ │ │ │ example, you do not want to load a mnesia backup from an untrusted entity. │ │ │ │ One issue with this being the potential for atom exhaustion, but more │ │ │ │ importantly you could potentially end up with a mnesia table containing │ │ │ │ harmful data (CWE-502). Other examples are dets and disk_log.

          JSON is an example of a better format to use when communicating with untrusted │ │ │ │ entities. Erlang/OTP provides the json module for JSON encoding/decoding. │ │ │ │ @@ -610,51 +610,51 @@ │ │ │ │ Language │ │ │ │ │ │ │ │

          LNG-001 - Prefer Tuples Over Exporting Variables

          For historical reasons, Erlang does not employ lexical scoping, and variables │ │ │ │ defined in "inner" expressions are available in "outer" expressions that follow │ │ │ │ them. Using this makes code harder to reason about, and it is preferable to │ │ │ │ write your code as if Erlang has lexical scoping by returning a tuple instead. │ │ │ │ There are no performance penalties for doing this.

          Rule priority: Recommendation

          %% DO
          │ │ │ │ -some_function(State0)
          │ │ │ │ -    {C, State1} = case foo(State0) of
          │ │ │ │ -                      {ok, A} ->
          │ │ │ │ -                          {a, bar(A)};
          │ │ │ │ +some_function(State0)
          │ │ │ │ +    {C, State1} = case foo(State0) of
          │ │ │ │ +                      {ok, A} ->
          │ │ │ │ +                          {a, bar(A)};
          │ │ │ │                        b ->
          │ │ │ │ -                          {b, State0}
          │ │ │ │ +                          {b, State0}
          │ │ │ │                    end,
          │ │ │ │ -    bar(C, State1).
          │ │ │ │ +    bar(C, State1).
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -some_function(State0)
          │ │ │ │ -    C = case foo(State0) of
          │ │ │ │ -            {ok, A} ->
          │ │ │ │ -                State1 = bar(A),
          │ │ │ │ +some_function(State0)
          │ │ │ │ +    C = case foo(State0) of
          │ │ │ │ +            {ok, A} ->
          │ │ │ │ +                State1 = bar(A),
          │ │ │ │                  a;
          │ │ │ │              b ->
          │ │ │ │                  State1 = State0,
          │ │ │ │                  b
          │ │ │ │          end,
          │ │ │ │ -    bar(C, State1).

          LNG-002 - Do Not Use catch

          The legacy catch construct cannot distinguish between throw/1 and a normal │ │ │ │ + bar(C, State1).

          LNG-002 - Do Not Use catch

          The legacy catch construct cannot distinguish between throw/1 and a normal │ │ │ │ return, which can have very unexpected results. For instance, the │ │ │ │ gen_server behavior will unintentionally accept any documented return value │ │ │ │ when thrown because of its use of catch.

          Instead, the modern try ... catch ... end │ │ │ │ construct should be used.

          Starting from Erlang/OTP 29, the compiler will by default raise │ │ │ │ warnings for uses of the legacy catch construct.

          Rule priority: Recommendation

          Related CWEs and OWASP risks: CWE-253, CWE-480, A10:2025

          %% DO
          │ │ │ │ -try operation(A, B) of
          │ │ │ │ +try operation(A, B) of
          │ │ │ │      C -> ...
          │ │ │ │  catch
          │ │ │ │      throw:Value ->
          │ │ │ │          ....;
          │ │ │ │      error:Reason ->
          │ │ │ │          ....
          │ │ │ │  end
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -case (catch operation(A, B)) of
          │ │ │ │ -    {'EXIT', Reason} ->
          │ │ │ │ +case (catch operation(A, B)) of
          │ │ │ │ +    {'EXIT', Reason} ->
          │ │ │ │          ...;
          │ │ │ │      C ->
          │ │ │ │          ...
          │ │ │ │  end

          LNG-003 - Do Not Use the Legacy and and or Operators

          These operators have been superseded by andalso and orelse, respectively.

          The legacy operators have higher precedence than in most other languages. For │ │ │ │ example X and Y =:= 3 is parsed as (X and Y) =:= 3. In a function body, │ │ │ │ this will crash, but when used in a guard it will silently fail. It can also │ │ │ │ unexpectedly corrupt the intended logic without crashing when all operands are │ │ │ │ @@ -723,38 +723,38 @@ │ │ │ │ leaking out through a crash or core dump.

          Rule priority: Medium

          Related CWEs and OWASP risks: CWE-209, CWE-532

          MSC-005 - Treat Match Specifications As Code

          Match specifications, such as those used with ETS, are vulnerable to injection │ │ │ │ attacks if they are constructed based on untrusted input.

          When untrusted data is matched verbatim (such as a key), it is important to │ │ │ │ wrap it in {const, UntrustedData} expressions. Building general queries based │ │ │ │ on untrusted data should be avoided, but if that cannot be done, the query │ │ │ │ should be the result of parsing the untrusted data into a match │ │ │ │ specification (where the final shape is controlled by the programmer), rather │ │ │ │ than attempting to validate the data before passing it in unaltered.

          Rule priority: High

          Related CWEs and OWASP risks: CWE-74

          %% DO
          │ │ │ │ -find(Table, Needle) ->
          │ │ │ │ -    ets:match(Table, {'_', {const, Needle}, '$1'}).
          │ │ │ │ +find(Table, Needle) ->
          │ │ │ │ +    ets:match(Table, {'_', {const, Needle}, '$1'}).
          │ │ │ │  
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -find(Table, Needle) ->
          │ │ │ │ -    ets:match(Table, {'_', Needle, '$1'}).

          MSC-006 - Consider "Link Following" Attacks

          When operating on untrusted file paths and trying to access files through them, │ │ │ │ +find(Table, Needle) -> │ │ │ │ + ets:match(Table, {'_', Needle, '$1'}).

          MSC-006 - Consider "Link Following" Attacks

          When operating on untrusted file paths and trying to access files through them, │ │ │ │ it is possible that the name does not actually identify a file, but a link │ │ │ │ instead, which can in turn point at an unintended resource which is potentially │ │ │ │ outside of the intended boundaries.

          This can be mitigated by using filelib:safe_relative_path/2 to ensure that │ │ │ │ the path does not escape the given bounds regardless of links. Note that it is │ │ │ │ impossible to guarantee atomicity across several filesystem operations, so care │ │ │ │ must be taken to avoid time-of-check time-of-use (TOCTOU) race conditions where │ │ │ │ a file or symbolic link is swapped out in the middle of these operations. When │ │ │ │ operating on a shared folder structure, ensure that only one entity has access │ │ │ │ to said structure.

          Rule priority: Medium

          Related CWEs and OWASP risks: CWE-22, CWE-59, CWE-61

          %% DO
          │ │ │ │ -open(UntrustedPath, Root, Opts) ->
          │ │ │ │ -    case filelib:safe_relative_path(UntrustedPath, Root) of
          │ │ │ │ -        unsafe -> {error, unsafe};
          │ │ │ │ -        Path -> file:open(filename:join(Root, Path), Opts)
          │ │ │ │ +open(UntrustedPath, Root, Opts) ->
          │ │ │ │ +    case filelib:safe_relative_path(UntrustedPath, Root) of
          │ │ │ │ +        unsafe -> {error, unsafe};
          │ │ │ │ +        Path -> file:open(filename:join(Root, Path), Opts)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %% DO NOT
          │ │ │ │ -file:open(UntrustedPath, Opts).

          MSC-007 - Avoid Using Debug Functionality in Production

          Functionality that has been explicitly marked to be used only for debugging, │ │ │ │ +file:open(UntrustedPath, Opts).

          MSC-007 - Avoid Using Debug Functionality in Production

          Functionality that has been explicitly marked to be used only for debugging, │ │ │ │ such as erlang:list_to_pid/1 or the keep_secrets │ │ │ │ ssl option should not be used in production environments, except during │ │ │ │ interactive debugging. Unlike with normal functionality, there are no promises │ │ │ │ of API stability for debug functionality, and they may change without notice. │ │ │ │ They sometimes also have adverse effects on system properties while used (such │ │ │ │ as greatly increasing scheduling latency), which are acceptable during │ │ │ │ testing but not in production.

          In production environments, debug functionality should be considered unsafe as │ │ │ ├── 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 returns 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 returns 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 of 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},{ClientPid2, 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 users
          │ │ │ │ -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:keyfind(From, 1, User_List) of
          │ │ │ │ +    case lists:keyfind(From, 1, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ -        {_, Name} ->
          │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ +        {_, 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:keyfind(To, 2, User_List) of
          │ │ │ │ +    case lists:keyfind(To, 2, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ │ -        {ToPid, To} ->
          │ │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ │ -            From ! {messenger, sent}
          │ │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ │ +        {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 process has done some illegal operation.

          If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ │ the server's 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 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 has 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 calls 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 a 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 (possibly 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 succeeded 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 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},{ClientPid2, 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 users
        │ │ │ │ -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:keyfind(From, 1, User_List) of
        │ │ │ │ +    case lists:keyfind(From, 1, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ -        {_, Name} ->
        │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
        │ │ │ │ +            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ +        {_, 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:keyfind(To, 2, User_List) of
        │ │ │ │ +    case lists:keyfind(To, 2, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ -        {ToPid, To} ->
        │ │ │ │ -            ToPid ! #message_from{from_name=Name, message=Message},
        │ │ │ │ -            From !  #server_reply{message=sent}
        │ │ │ │ +            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ +        {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 in which 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 in which you assign values to the │ │ │ │ various parts of a record 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 files (.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 accesses the │ │ │ │ name and address fields of the record:

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

        │ │ │ │

        The 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 are "" 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 a 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, the 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 │ │ │ │ compatibility.

          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 syntax similar to that 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 │ │ │ │ parentheses 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 catch misspellings 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 @@ │ │ │ │ │ │ │ │ │ │ │ │ Macro 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 a macro can be removed as follows:

      -undef(Macro).

      │ │ │ │ +

      A definition of a 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 pseudo-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 provides 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 provides the user │ │ │ │ +with some simple trace output.

      Example:

      -module(m)
      │ │ │ │  ...
      │ │ │ │ --if(?OTP_RELEASE >= 26).
      │ │ │ │ +-if(?OTP_RELEASE >= 26).
      │ │ │ │  %% Code that will work in OTP 26 or higher
      │ │ │ │ --elif(?OTP_RELEASE >= 25).
      │ │ │ │ +-elif(?OTP_RELEASE >= 25).
      │ │ │ │  %% Code that will work in OTP 25 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 versions 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 versions 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,37 +22,37 @@ │ │ │ │ │ │ │ │

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

Finally, multiple elements can be emitted by the list comprehension in each iteration:

> [X, X + 100 || X <:- [1, 2, 3]].
│ │ │ │ -[1,101,2,102,3,103]

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

Finally, multiple elements can be emitted by the list comprehension in each iteration:

> [X, X + 100 || X <:- [1, 2, 3]].
│ │ │ │ +[1,101,2,102,3,103]

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 │ │ │ │ @@ -60,127 +60,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 tuples {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 happens 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 comprehension 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 comprehension 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 to skip 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 outputs 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 comprehension. │ │ │ │ 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 29 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 shut down, 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 have 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 than │ │ │ │ -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 lists 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 lists 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 │ │ │ │ -L1 = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ -    splitwith(Pred, L, []).
│ │ │ │ +L1 = 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 types 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 Toks 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 compiled.

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

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

│ │ │ │ -
ExprM:ExprF(Expr1,...,ExprN)
│ │ │ │ -ExprF(Expr1,...,ExprN)

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ +

ExprM:ExprF(Expr1,...,ExprN)
│ │ │ │ +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 silently change the behavior of old code.

However, to avoid old (pre R14) code changing 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, an 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 matches │ │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ │ the clauses are 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, namely when │ │ │ │ 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 messages received after │ │ │ │ the reference was created need 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 caveat as in │ │ │ │ the warning above applies.

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 a 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 syntax similar to 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 can be 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 can be 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' were 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' were 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 are 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 with 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 is 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 is 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 to 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 to 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.
  • Pass 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 @@ │ │ │ │ ExceptionBody │ │ │ │ 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 the of section are:

  • unbound in the catch section
  • unsafe in 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 of 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 MapExpression 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].
│ │ │ │ -[{b,2}]

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].
│ │ │ │ +[{b,2}]

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 backtrace (stacktrace) │ │ │ │

│ │ │ │

The stack backtrace (stacktrace) is a list that │ │ │ │ contains {Module, Function, Arity, ExtraInfo} and/or {Fun, Arity, ExtraInfo} │ │ │ ├── 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 of 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 is │ │ │ │ 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 matches 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 can be running │ │ │ │ at the same time.

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 attributes 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 detail. For example:

-module(arith).
│ │ │ │ +module and then go into greater detail. 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:subtract(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 the entity, │ │ │ │ and then go into greater detail if 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)). {arith, 3}
│ │ │ │  ```
│ │ │ │  """.
│ │ │ │ --spec add(number(), number()) -> number().
│ │ │ │ -add({arith, One}, {arith, Two}) -> {arith, One + Two}.

Examples in documentation can be tested using ct_doctest.

│ │ │ │ +-spec add(number(), number()) -> number(). │ │ │ │ +add({arith, One}, {arith, Two}) -> {arith, One + Two}.

Examples in documentation can be tested using ct_doctest.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Doc metadata │ │ │ │

│ │ │ │

There are four reserved metadata keys for -doc:

  • since => unicode:chardata() - Shows in which version of the application the │ │ │ │ function, type, or callback 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) :: {arith, 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) :: {arith, Value}.
│ │ │ │  %% signature will be `number(Value)`
│ │ │ │  
│ │ │ │ --opaque number() :: {arith, number()}.
│ │ │ │ +-opaque number() :: {arith, 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.

This will create the signature add(One, Two). The signature will be removed from the │ │ │ │ +add(A, B) -> A + B.

This 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 │ │ │ │ @@ -48,25 +48,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, and consists of alphanumerics, -, _, and \. │ │ │ │ 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 at 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 illustrates 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 an 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 test whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │ +BIFs test 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 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 are 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 are 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 are 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 are 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(M2).
    │ │ │ │ +3> maps:get(date, M1).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ │ +5> map_size(M2).
    │ │ │ │  3
    │ │ │ │ -6> map_size(#{}).
    │ │ │ │ +6> map_size(#{}).
    │ │ │ │  0

    A collection of map-processing functions can be found in the 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 to 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 can be found in the module │ │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │ │

    │ │ │ │ @@ -419,64 +419,64 @@ │ │ │ │ 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.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Native Record │ │ │ │

    │ │ │ │

    A native record is a data structure for storing a fixed number │ │ │ │ of elements. It is similar to the traditional tuple-based records, │ │ │ │ -except that it is a true data type.

    Examples:

    -module(person).
    │ │ │ │ --export([new/2]).
    │ │ │ │ +except that it is a true data type.

    Examples:

    -module(person).
    │ │ │ │ +-export([new/2]).
    │ │ │ │  
    │ │ │ │ --record #person{name, age}.
    │ │ │ │ +-record #person{name, age}.
    │ │ │ │  
    │ │ │ │ -new(Name, Age) ->
    │ │ │ │ -    #person{name=Name, age=Age}.
    1> P = person:new(ernie, 44).
    │ │ │ │ -#person:person{name = ernie,age = 44}
    │ │ │ │ -2> is_record(P).
    │ │ │ │ +new(Name, Age) ->
    │ │ │ │ +    #person{name=Name, age=Age}.
    1> P = person:new(ernie, 44).
    │ │ │ │ +#person:person{name = ernie,age = 44}
    │ │ │ │ +2> is_record(P).
    │ │ │ │  true
    │ │ │ │ -3> is_tuple(P).
    │ │ │ │ +3> is_tuple(P).
    │ │ │ │  false
    │ │ │ │ -4> is_map(P).
    │ │ │ │ +4> is_map(P).
    │ │ │ │  false

    Warning

    Native records are considered experimental in Erlang/OTP 29. This │ │ │ │ means that their behavior may change, potentially requiring updates │ │ │ │ to applications that use them.

    Change

    Native records were introduced in Erlang/OTP 29.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ │ @@ -494,44 +494,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 have 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,287 +197,287 @@ │ │ │ │ {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"],
    │ │ │ │ -  old}]

    We see that the new release version is permanent, so it would be safe to │ │ │ │ +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"],
    │ │ │ │ +  old}]

    We see that the new release version is permanent, so it would be safe to │ │ │ │ restart the node.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 - 29.0-rc3 │ │ │ │ │ - urn:uuid:1745e863-a49a-b90b-ffa6-0afe165779fa │ │ │ │ │ + urn:uuid:3da5c23f-9d8b-9b62-01c2-5b7aca6be736 │ │ │ │ │ en │ │ │ │ │ - 2026-04-15T20:07:07Z │ │ │ │ │ + 2026-04-21T04:13:39Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -87,22 +87,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 the 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 ends 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 need to be 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 addresses 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/OTP 28 [erts-16.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │  Eshell V16.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │  (ping@kosken)1>

    On gollum:

    gollum$ erl -sname pong
    │ │ │ │  Erlang/OTP 28 [erts-16.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │  Eshell V16.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ -(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 executed:

    {ping, Ping_PID} ->
    │ │ │ │ -    io:format("Pong received ping~n", []),
    │ │ │ │ +node the "ping" process is executed:

    {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
    │ │ │ │ @@ -418,184 +418,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},{ClientPid2, 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 users
    │ │ │ │ -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:keyfind(From, 1, User_List) of
    │ │ │ │ +    case lists:keyfind(From, 1, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ -        {From, Name} ->
    │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ +        {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:keyfind(To, 2, User_List) of
    │ │ │ │ +    case lists:keyfind(To, 2, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ │ -        {ToPid, To} ->
    │ │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ │ -            From ! {messenger, sent}
    │ │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ │ +        {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,List) 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:keyfind 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:keyfind is like lists:keymember, but it returns │ │ │ │ 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:keyfind(From, 1, User_List)

    If keyfind 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:keyfind(From, 1, User_List)

    If keyfind 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 keyfind returns {From,Name} it is certain that │ │ │ │ -the user is logged on and that their name (peter) is in the 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 their name (peter) is in the 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 keyfind is done on User_List to find │ │ │ │ -the pid of the client corresponding to fred:

    lists:keyfind(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:keyfind(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 keyfind returns:

    {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 keyfind returns:

    {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 a 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 a 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 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 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,49 +65,49 @@ │ │ │ │ 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=SharedSubTerms}.

    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=SharedSubTerms}.

    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 of 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, binary_to_atom/1,2 │ │ │ │

    │ │ │ │

    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 │ │ │ │ @@ -116,28 +116,28 @@ │ │ │ │ input, list_to_existing_atom/1, │ │ │ │ binary_to_existing_atom/1, or │ │ │ │ binary_to_existing_atom/2 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, binary_to_atom/1, or │ │ │ │ binary_to_atom/2 to construct an atom that │ │ │ │ -is passed to apply/3 is quite expensive.

    DO NOT

    apply(list_to_atom("some_prefix"++Var), foo, Args)

    DO NOT

    apply(binary_to_atom(<<"some_prefix", Var/binary>>), foo, Args)

    DO NOT

    apply(binary_to_atom(<<"some_prefix", Var/binary>>, utf8), foo, Args)

    │ │ │ │ +is passed to apply/3 is quite expensive.

    DO NOT

    apply(list_to_atom("some_prefix"++Var), foo, Args)

    DO NOT

    apply(binary_to_atom(<<"some_prefix", Var/binary>>), foo, Args)

    DO NOT

    apply(binary_to_atom(<<"some_prefix", Var/binary>>, utf8), 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 │ │ │ │ @@ -148,18 +148,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 and 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 a 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 are 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 are 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 has 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 logger.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── 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-bit 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 consists │ │ │ │ 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 │ │ │ │ parentheses 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 as 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 as 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 advantage 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 adds 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 does nothing if Buffer is already a binary).

        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 a 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 a 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 a 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 a 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 │ │ │ │ an 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 │ │ │ │ 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,86 +176,86 @@ │ │ │ │ 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 │ │ │ │ @@ -275,29 +275,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 │ │ │ │ @@ -312,26 +312,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 │ │ │ │ has 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.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -386,74 +386,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 be 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 │ │ │ │ @@ -461,20 +461,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 │ │ │ an 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 │ │ │ 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,86 +271,86 @@ │ │ │ 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 │ │ │ @@ -370,29 +370,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 │ │ │ @@ -407,26 +407,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 │ │ │ has 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.

    │ │ │ │ │ │ │ │ │ @@ -481,74 +481,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 be 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 │ │ │ @@ -556,22 +556,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 a 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 a 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 a 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 a 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 advantage 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 adds 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 does nothing if Buffer is already a binary).

          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-bit 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 consists │ │ │ 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 │ │ │ parentheses 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 as 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 as 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 and 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 a 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 are 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 are 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 has 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 logger.

          │ │ │

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

          │ │ │ │ │ │ │ │ │ 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 a 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 a 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 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 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,49 +160,49 @@ │ │ │ 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=SharedSubTerms}.

          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=SharedSubTerms}.

          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 of 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, binary_to_atom/1,2 │ │ │

          │ │ │

          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 │ │ │ @@ -211,28 +211,28 @@ │ │ │ input, list_to_existing_atom/1, │ │ │ binary_to_existing_atom/1, or │ │ │ binary_to_existing_atom/2 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, binary_to_atom/1, or │ │ │ binary_to_atom/2 to construct an atom that │ │ │ -is passed to apply/3 is quite expensive.

          DO NOT

          apply(list_to_atom("some_prefix"++Var), foo, Args)

          DO NOT

          apply(binary_to_atom(<<"some_prefix", Var/binary>>), foo, Args)

          DO NOT

          apply(binary_to_atom(<<"some_prefix", Var/binary>>, utf8), foo, Args)

          │ │ │ +is passed to apply/3 is quite expensive.

          DO NOT

          apply(list_to_atom("some_prefix"++Var), foo, Args)

          DO NOT

          apply(binary_to_atom(<<"some_prefix", Var/binary>>), foo, Args)

          DO NOT

          apply(binary_to_atom(<<"some_prefix", Var/binary>>, utf8), 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 │ │ │ @@ -243,18 +243,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 the 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 ends 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 need to be 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 addresses 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/OTP 28 [erts-16.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │  Eshell V16.3 (press Ctrl+G to abort, type help(). for help)
          │ │ │  (ping@kosken)1>

          On gollum:

          gollum$ erl -sname pong
          │ │ │  Erlang/OTP 28 [erts-16.3] [source] [64-bit] [smp:16:16] [ds:16:16:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │  Eshell V16.3 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -(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 executed:

          {ping, Ping_PID} ->
          │ │ │ -    io:format("Pong received ping~n", []),
          │ │ │ +node the "ping" process is executed:

          {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
          │ │ │ @@ -513,188 +513,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},{ClientPid2, 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 users
          │ │ │ -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:keyfind(From, 1, User_List) of
          │ │ │ +    case lists:keyfind(From, 1, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ -        {From, Name} ->
          │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ +        {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:keyfind(To, 2, User_List) of
          │ │ │ +    case lists:keyfind(To, 2, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ -        {ToPid, To} ->
          │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ -            From ! {messenger, sent}
          │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ +        {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,List) 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:keyfind 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:keyfind is like lists:keymember, but it returns │ │ │ 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:keyfind(From, 1, User_List)

          If keyfind 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:keyfind(From, 1, User_List)

          If keyfind 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 keyfind returns {From,Name} it is certain that │ │ │ -the user is logged on and that their name (peter) is in the 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 their name (peter) is in the 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 keyfind is done on User_List to find │ │ │ -the pid of the client corresponding to fred:

          lists:keyfind(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:keyfind(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 keyfind returns:

          {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 keyfind returns:

          {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 have 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,291 +292,291 @@ │ │ │ {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"],
      │ │ │ -  old}]

      We see that the new release version is permanent, so it would be safe to │ │ │ +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"],
      │ │ │ +  old}]

      We see that the new release version is permanent, so it would be safe to │ │ │ restart the node.

      │ │ │ │ │ │ │ │ │ │ │ │ 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 an 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 test whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ +BIFs test 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 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 are 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 are 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 are 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 are 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(M2).
    │ │ │ +3> maps:get(date, M1).
    │ │ │ +{july,29}
    │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ +5> map_size(M2).
    │ │ │  3
    │ │ │ -6> map_size(#{}).
    │ │ │ +6> map_size(#{}).
    │ │ │  0

    A collection of map-processing functions can be found in the 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 to 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 can be found in the module │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │

    │ │ │ @@ -514,64 +514,64 @@ │ │ │ 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.

    │ │ │ │ │ │ │ │ │ │ │ │ Native Record │ │ │

    │ │ │

    A native record is a data structure for storing a fixed number │ │ │ of elements. It is similar to the traditional tuple-based records, │ │ │ -except that it is a true data type.

    Examples:

    -module(person).
    │ │ │ --export([new/2]).
    │ │ │ +except that it is a true data type.

    Examples:

    -module(person).
    │ │ │ +-export([new/2]).
    │ │ │  
    │ │ │ --record #person{name, age}.
    │ │ │ +-record #person{name, age}.
    │ │ │  
    │ │ │ -new(Name, Age) ->
    │ │ │ -    #person{name=Name, age=Age}.
    1> P = person:new(ernie, 44).
    │ │ │ -#person:person{name = ernie,age = 44}
    │ │ │ -2> is_record(P).
    │ │ │ +new(Name, Age) ->
    │ │ │ +    #person{name=Name, age=Age}.
    1> P = person:new(ernie, 44).
    │ │ │ +#person:person{name = ernie,age = 44}
    │ │ │ +2> is_record(P).
    │ │ │  true
    │ │ │ -3> is_tuple(P).
    │ │ │ +3> is_tuple(P).
    │ │ │  false
    │ │ │ -4> is_map(P).
    │ │ │ +4> is_map(P).
    │ │ │  false

    Warning

    Native records are considered experimental in Erlang/OTP 29. This │ │ │ means that their behavior may change, potentially requiring updates │ │ │ to applications that use them.

    Change

    Native records were introduced in Erlang/OTP 29.

    │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ @@ -589,47 +589,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 │ │ │ @@ -143,25 +143,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, and consists of alphanumerics, -, _, and \. │ │ │ 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 at 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 attributes 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 detail. For example:

    -module(arith).
    │ │ │ +module and then go into greater detail. 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:subtract(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 the entity, │ │ │ and then go into greater detail if 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)). {arith, 3}
    │ │ │  ```
    │ │ │  """.
    │ │ │ --spec add(number(), number()) -> number().
    │ │ │ -add({arith, One}, {arith, Two}) -> {arith, One + Two}.

    Examples in documentation can be tested using ct_doctest.

    │ │ │ +-spec add(number(), number()) -> number(). │ │ │ +add({arith, One}, {arith, Two}) -> {arith, One + Two}.

    Examples in documentation can be tested using ct_doctest.

    │ │ │ │ │ │ │ │ │ │ │ │ Doc metadata │ │ │

    │ │ │

    There are four reserved metadata keys for -doc:

    • since => unicode:chardata() - Shows in which version of the application the │ │ │ function, type, or callback 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) :: {arith, 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) :: {arith, Value}.
    │ │ │  %% signature will be `number(Value)`
    │ │ │  
    │ │ │ --opaque number() :: {arith, number()}.
    │ │ │ +-opaque number() :: {arith, 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.

    This will create the signature add(One, Two). The signature will be removed from the │ │ │ +add(A, B) -> A + B.

    This 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 can be running │ │ │ at the same time.

    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 is │ │ │ 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 matches 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 of 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
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ 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 backtrace (stacktrace) │ │ │

    │ │ │

    The stack backtrace (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 compiled.

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

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

    │ │ │ -
    ExprM:ExprF(Expr1,...,ExprN)
    │ │ │ -ExprF(Expr1,...,ExprN)

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ +

    ExprM:ExprF(Expr1,...,ExprN)
    │ │ │ +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 silently change the behavior of old code.

    However, to avoid old (pre R14) code changing 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, an 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 matches │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ the clauses are 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, namely when │ │ │ 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 messages received after │ │ │ the reference was created need 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 caveat as in │ │ │ the warning above applies.

    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 a 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 syntax similar to 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 can be 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 can be 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' were 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' were 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 are 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 with 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 is 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 is 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 to 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 to 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.
    • Pass 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 @@ │ │ │ ExceptionBody │ │ │ 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 the of section are:

    • unbound in the catch section
    • unsafe in 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 of 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 MapExpression 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].
    │ │ │ -[{b,2}]

    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].
    │ │ │ +[{b,2}]

    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 have 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 than │ │ │ -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 lists 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 lists 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 │ │ │ -L1 = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ -    splitwith(Pred, L, []).
    │ │ │ +L1 = 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 types 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 Toks 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 shut down, 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,37 +117,37 @@ │ │ │ │ │ │

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

      Finally, multiple elements can be emitted by the list comprehension in each iteration:

      > [X, X + 100 || X <:- [1, 2, 3]].
      │ │ │ -[1,101,2,102,3,103]

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

      Finally, multiple elements can be emitted by the list comprehension in each iteration:

      > [X, X + 100 || X <:- [1, 2, 3]].
      │ │ │ +[1,101,2,102,3,103]

      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 │ │ │ @@ -155,127 +155,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 tuples {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 happens 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 comprehension 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 comprehension 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 to skip 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 outputs 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 comprehension. │ │ │ 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 versions 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 versions 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 @@ │ │ │ │ │ │ │ │ │ Macro 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 a macro can be removed as follows:

    -undef(Macro).

    │ │ │ +

    A definition of a 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 pseudo-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 provides 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 provides the user │ │ │ +with some simple trace output.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 26).
    │ │ │ +-if(?OTP_RELEASE >= 26).
    │ │ │  %% Code that will work in OTP 26 or higher
    │ │ │ --elif(?OTP_RELEASE >= 25).
    │ │ │ +-elif(?OTP_RELEASE >= 25).
    │ │ │  %% Code that will work in OTP 25 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 syntax similar to that 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 │ │ │ parentheses 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 │ │ │ compatibility.

    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, the 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 accesses the │ │ │ name and address fields of the record:

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

    │ │ │

    The 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 are "" 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 a 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 in which 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 in which you assign values to the │ │ │ various parts of a record 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 files (.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 succeeded 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 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 (possibly 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 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 has 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 calls 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 returns 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 returns 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 of 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},{ClientPid2, 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 users
    │ │ │ -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:keyfind(From, 1, User_List) of
    │ │ │ +    case lists:keyfind(From, 1, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ -        {_, Name} ->
    │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ +        {_, 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:keyfind(To, 2, User_List) of
    │ │ │ +    case lists:keyfind(To, 2, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ -        {ToPid, To} ->
    │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ -            From ! {messenger, sent}
    │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ +        {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 process has done some illegal operation.

    If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ the server's 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/secure_coding.html │ │ │ @@ -374,108 +374,108 @@ │ │ │ left to the supervision structure, and all unexpected conditions should be │ │ │ considered errors. In brief, this is because encountering something unexpected │ │ │ means that we have left the known and tested path, and continuing greatly │ │ │ increases the risk for bugs and security issues.

    Erlang code should be written as restrictively as possible, to provoke errors │ │ │ whenever anything unexpected happens. The idea is to make the third error │ │ │ category, program bugs, visible as a crash instead of silently continuing.

    Rule priority: High

    Related CWEs and OWASP risks: CWE-252, CWE-253, CWE-391, CWE-392, │ │ │ CWE-394, CWE-396, A10:2025

    %% DO
    │ │ │ -case operation(A, B) of
    │ │ │ +case operation(A, B) of
    │ │ │      true -> C;
    │ │ │      false -> D
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -case operation(A, B) of
    │ │ │ +case operation(A, B) of
    │ │ │      true -> C;
    │ │ │      %% What if operation/2 is extended to also return 'maybe', or someone
    │ │ │      %% misspells 'true' as 'tru'?
    │ │ │      _ -> D
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -ok = file:write(Fd, Data)
    │ │ │ +ok = file:write(Fd, Data)
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -_ = file:write(Fd, Data)
    │ │ │ +_ = file:write(Fd, Data)
    │ │ │  
    │ │ │  %% DO
    │ │ │ -foo([First | Rest]) ->
    │ │ │ -    [bar(First) | foo(Rest)];
    │ │ │ -foo([]) ->
    │ │ │ -    [].
    │ │ │ +foo([First | Rest]) ->
    │ │ │ +    [bar(First) | foo(Rest)];
    │ │ │ +foo([]) ->
    │ │ │ +    [].
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -foo([First | Rest]) ->
    │ │ │ -    [bar(First) | foo(Rest)];
    │ │ │ -foo(_) ->
    │ │ │ -    [].
    │ │ │ +foo([First | Rest]) ->
    │ │ │ +    [bar(First) | foo(Rest)];
    │ │ │ +foo(_) ->
    │ │ │ +    [].
    │ │ │  
    │ │ │  %% DO
    │ │ │ -input_to_atom(<<"foo">>) -> foo;
    │ │ │ -input_to_atom(<<"bar">>) -> bar;
    │ │ │ -input_to_atom(<<"quux">>) -> quux.
    │ │ │ +input_to_atom(<<"foo">>) -> foo;
    │ │ │ +input_to_atom(<<"bar">>) -> bar;
    │ │ │ +input_to_atom(<<"quux">>) -> quux.
    │ │ │  
    │ │ │  %% DO NOT, when set of possible atoms is known beforehand
    │ │ │ -input_to_atom(Text) -> binary_to_existing_atom(Text).
    │ │ │ +input_to_atom(Text) -> binary_to_existing_atom(Text).
    │ │ │  
    │ │ │  %% DO
    │ │ │ -try operation(A, B) of
    │ │ │ -    {ok, X} -> something(X)
    │ │ │ +try operation(A, B) of
    │ │ │ +    {ok, X} -> something(X)
    │ │ │  catch
    │ │ │      error:specific_error -> error
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -try operation(A, B) of
    │ │ │ -    {ok, X} -> something(X)
    │ │ │ +try operation(A, B) of
    │ │ │ +    {ok, X} -> something(X)
    │ │ │  catch
    │ │ │      error:_ -> error
    │ │ │  end.
    │ │ │  
    │ │ │  %% PREFER
    │ │ │ -case my_filter(List0, unchanged) of
    │ │ │ +case my_filter(List0, unchanged) of
    │ │ │      unchanged -> List0;
    │ │ │ -    {changed, List} -> List
    │ │ │ +    {changed, List} -> List
    │ │ │  end
    │ │ │  
    │ │ │  %% AVOID
    │ │ │ -case my_filter(List0, unchanged) of
    │ │ │ +case my_filter(List0, unchanged) of
    │ │ │      unchanged -> List0;
    │ │ │      %% What if a misspelled atom like 'uchanged' is returned?
    │ │ │      List -> List
    │ │ │  end
    │ │ │  
    │ │ │  %% PREFER
    │ │ │ -[op(L) || #my_record{}=L <:- ListOfMyRecord]
    │ │ │ +[op(L) || #my_record{}=L <:- ListOfMyRecord]
    │ │ │  
    │ │ │  %% AVOID, this silently filters out entries that do not match #my_record{}
    │ │ │ -[op(L) || #my_record{}=L <- ListOfMyRecord]

    STL-002 - Avoid Boolean Blindness

    Whenever boolean values have a context, prefer using more descriptive atoms to │ │ │ +[op(L) || #my_record{}=L <- ListOfMyRecord]

    STL-002 - Avoid Boolean Blindness

    Whenever boolean values have a context, prefer using more descriptive atoms to │ │ │ express the boolean value, for example initialized/uninitialized or │ │ │ changed/unchanged. This makes it easier to distinguish between different │ │ │ boolean variables when many of them are used together, especially when matching │ │ │ in function heads and the like.

    Rule priority: Recommendation

    Related CWEs and OWASP risks: CWE-628

    %% DO
    │ │ │ -case my_filter(List0, unchanged) of
    │ │ │ +case my_filter(List0, unchanged) of
    │ │ │      unchanged -> List0;
    │ │ │ -    {changed, List} -> List
    │ │ │ +    {changed, List} -> List
    │ │ │  end
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -case my_filter(List0, false) of
    │ │ │ +case my_filter(List0, false) of
    │ │ │      false -> List0;
    │ │ │ -    {true, List} -> List
    │ │ │ +    {true, List} -> List
    │ │ │  end

    STL-003 - Use Uppercase Names for Macros

    Macros are distinguished by a ? prefix, so an accidental omission of the │ │ │ prefix leaves the name there instead of applying the macro. For example, │ │ │ function_call(?my_macro, SomeArg) becomes function_call(my_macro, SomeArg) │ │ │ which is syntactically valid, hiding the error.

    Static analysis tools can often find these issues, but a quicker way to find │ │ │ them is to adopt the convention that all macros should be upper-case. Missing a │ │ │ ? will in most cases then lead to an unbound variable error or similar.

    Rule priority: Recommendation

    %% DO
    │ │ │ --define(MY_MACRO, 65535).
    │ │ │ +-define(MY_MACRO, 65535).
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ --define(my_macro, 65535).

    │ │ │ +-define(my_macro, 65535).

    │ │ │ │ │ │ │ │ │ │ │ │ Deployment │ │ │

    │ │ │

    DEP-001 - Do Not Expose Default Erlang Distribution on Untrusted Networks

    The builtin Erlang distribution makes it possible to easily and transparently │ │ │ communicate between Erlang nodes. By default, communication is performed over │ │ │ @@ -568,27 +568,27 @@ │ │ │ pervasive error handling throughout the code at large is drastically reduced, │ │ │ and with it a large source of bugs and security issues.

    Rule priority: Medium

    Related CWEs and OWASP risks: CWE-389, CWE-544, CWE-653, A10:2025

    DSG-002 - Prefer Letting the User Decide What Warrants an Exception

    Prefer to design your interfaces so that the user decides whether an error is │ │ │ exceptional or not, by following the {ok, Result} | {error, Reason} │ │ │ convention. Generally speaking the user of an interface has more context than │ │ │ the one implementing it, and giving them the freedom to choose through pattern │ │ │ matching tends to result in clearer code as the handling of raised exceptions │ │ │ is more difficult to follow.

    Rule priority: Recommendation

    Related CWEs and OWASP risks: CWE-389, A10:2025

    %% PREFER
    │ │ │ -{ok, C} = some_function(A, B)
    │ │ │ +{ok, C} = some_function(A, B)
    │ │ │  
    │ │ │  %% PREFER
    │ │ │ -case some_function(A, B) of
    │ │ │ -    {ok, C} ->
    │ │ │ +case some_function(A, B) of
    │ │ │ +    {ok, C} ->
    │ │ │          %% Happy path
    │ │ │          ...;
    │ │ │ -    {error, Error} ->
    │ │ │ +    {error, Error} ->
    │ │ │          %% Handle it
    │ │ │  end
    │ │ │  
    │ │ │  %% AVOID
    │ │ │ -try some_function(A, B) of
    │ │ │ +try some_function(A, B) of
    │ │ │      C -> 
    │ │ │          %% Happy path
    │ │ │          ...
    │ │ │  catch
    │ │ │      error:_ ->
    │ │ │          %% Handle it
    │ │ │  end

    DSG-003 - Do Not Abuse Atoms

    Atoms are designed to provide an easy way to create named constants in code. │ │ │ @@ -610,23 +610,23 @@ │ │ │ DSG-011) unless the API also provides some way of preventing creation of │ │ │ atoms. For example, binary_to_term/2 with the safe option will prevent │ │ │ new atoms from being created. However, note that even if the safe option is │ │ │ used and the data originates from an untrusted source, it still has to be │ │ │ validated and sanitized, since it can still be harmful to the Erlang │ │ │ application in other ways.

    In general, it is best to avoid using such functions altogether on untrusted │ │ │ data, even with the safe option.

    Rule priority: High

    Related CWEs and OWASP risks: CWE-770, API10:2023

    %% DO, AND PREFER (see STL-001)
    │ │ │ -input_to_atom(<<"foo">>) -> foo;
    │ │ │ -input_to_atom(<<"bar">>) -> bar;
    │ │ │ -input_to_atom(<<"quux">>) -> quux.
    │ │ │ +input_to_atom(<<"foo">>) -> foo;
    │ │ │ +input_to_atom(<<"bar">>) -> bar;
    │ │ │ +input_to_atom(<<"quux">>) -> quux.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -input_to_atom(Text) -> binary_to_existing_atom(Text).
    │ │ │ +input_to_atom(Text) -> binary_to_existing_atom(Text).
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -input_to_atom(Text) -> binary_to_atom(Text).

    DSG-004 - Do Not Use Undocumented Functionality

    Undocumented functions or functionality must never be used. This includes │ │ │ +input_to_atom(Text) -> binary_to_atom(Text).

    DSG-004 - Do Not Use Undocumented Functionality

    Undocumented functions or functionality must never be used. This includes │ │ │ undocumented arguments to documented functions and undocumented system │ │ │ services. Using such functionality poses a serious security risk. These │ │ │ functions and features are intended strictly for internal use within Erlang/OTP │ │ │ and are not supported for external use.

    Merely passing the wrong arguments to these functions can cause the system to │ │ │ behave in unexpected ways from that point on, and their behavior may change or │ │ │ they may be removed without prior notice.

    Rule priority: Critical

    Related CWEs and OWASP risks: CWE-242, CWE-477, CWE-676

    DSG-005 - Do Not Use Deprecated Functionality

    When functionality is deprecated in Erlang/OTP, the documentation will │ │ │ typically point to other or new functionality to use instead. The deprecation │ │ │ @@ -667,28 +667,28 @@ │ │ │ them through their name instead of their process or table identifier. This │ │ │ should be used with care, as a process or table may terminate at any time. │ │ │ For example, if two messages are sent to a process through a registered name, │ │ │ the second message may arrive to a newly restarted process that has not seen │ │ │ the first, which may be significant (CWE-386).

    To prevent these issues, either redesign your interface so that multiple │ │ │ messages or lookups are not necessary, or look up the identifier from the │ │ │ registered name and use the identifier instead.

    Rule priority: Recommendation

    Related CWEs and OWASP risks: CWE-386

    %% DO
    │ │ │ -Pid = whereis(registered_process),
    │ │ │ +Pid = whereis(registered_process),
    │ │ │  Pid ! hello,
    │ │ │  Pid ! world.
    │ │ │  
    │ │ │ -Tid = ets:whereis(registered_table),
    │ │ │ -A = ets:lookup(Tid, KeyA),
    │ │ │ -B = ets:lookup(Tid, KeyB).
    │ │ │ +Tid = ets:whereis(registered_table),
    │ │ │ +A = ets:lookup(Tid, KeyA),
    │ │ │ +B = ets:lookup(Tid, KeyB).
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │  registered_process ! hello,
    │ │ │  registered_process ! world.
    │ │ │  
    │ │ │ -A = ets:lookup(registered_table, KeyA),
    │ │ │ -B = ets:lookup(registered_table, KeyB).

    DSG-011 - Only Deserialize Trusted Data

    Erlang/OTP provides various functionality that serializes and deserializes │ │ │ +A = ets:lookup(registered_table, KeyA), │ │ │ +B = ets:lookup(registered_table, KeyB).

    DSG-011 - Only Deserialize Trusted Data

    Erlang/OTP provides various functionality that serializes and deserializes │ │ │ general Erlang terms. Such functionality is intended to be used in a trusted │ │ │ environment and is not suitable for communication with untrusted entities. For │ │ │ example, you do not want to load a mnesia backup from an untrusted entity. │ │ │ One issue with this being the potential for atom exhaustion, but more │ │ │ importantly you could potentially end up with a mnesia table containing │ │ │ harmful data (CWE-502). Other examples are dets and disk_log.

    JSON is an example of a better format to use when communicating with untrusted │ │ │ entities. Erlang/OTP provides the json module for JSON encoding/decoding. │ │ │ @@ -705,51 +705,51 @@ │ │ │ Language │ │ │ │ │ │

    LNG-001 - Prefer Tuples Over Exporting Variables

    For historical reasons, Erlang does not employ lexical scoping, and variables │ │ │ defined in "inner" expressions are available in "outer" expressions that follow │ │ │ them. Using this makes code harder to reason about, and it is preferable to │ │ │ write your code as if Erlang has lexical scoping by returning a tuple instead. │ │ │ There are no performance penalties for doing this.

    Rule priority: Recommendation

    %% DO
    │ │ │ -some_function(State0)
    │ │ │ -    {C, State1} = case foo(State0) of
    │ │ │ -                      {ok, A} ->
    │ │ │ -                          {a, bar(A)};
    │ │ │ +some_function(State0)
    │ │ │ +    {C, State1} = case foo(State0) of
    │ │ │ +                      {ok, A} ->
    │ │ │ +                          {a, bar(A)};
    │ │ │                        b ->
    │ │ │ -                          {b, State0}
    │ │ │ +                          {b, State0}
    │ │ │                    end,
    │ │ │ -    bar(C, State1).
    │ │ │ +    bar(C, State1).
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -some_function(State0)
    │ │ │ -    C = case foo(State0) of
    │ │ │ -            {ok, A} ->
    │ │ │ -                State1 = bar(A),
    │ │ │ +some_function(State0)
    │ │ │ +    C = case foo(State0) of
    │ │ │ +            {ok, A} ->
    │ │ │ +                State1 = bar(A),
    │ │ │                  a;
    │ │ │              b ->
    │ │ │                  State1 = State0,
    │ │ │                  b
    │ │ │          end,
    │ │ │ -    bar(C, State1).

    LNG-002 - Do Not Use catch

    The legacy catch construct cannot distinguish between throw/1 and a normal │ │ │ + bar(C, State1).

    LNG-002 - Do Not Use catch

    The legacy catch construct cannot distinguish between throw/1 and a normal │ │ │ return, which can have very unexpected results. For instance, the │ │ │ gen_server behavior will unintentionally accept any documented return value │ │ │ when thrown because of its use of catch.

    Instead, the modern try ... catch ... end │ │ │ construct should be used.

    Starting from Erlang/OTP 29, the compiler will by default raise │ │ │ warnings for uses of the legacy catch construct.

    Rule priority: Recommendation

    Related CWEs and OWASP risks: CWE-253, CWE-480, A10:2025

    %% DO
    │ │ │ -try operation(A, B) of
    │ │ │ +try operation(A, B) of
    │ │ │      C -> ...
    │ │ │  catch
    │ │ │      throw:Value ->
    │ │ │          ....;
    │ │ │      error:Reason ->
    │ │ │          ....
    │ │ │  end
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -case (catch operation(A, B)) of
    │ │ │ -    {'EXIT', Reason} ->
    │ │ │ +case (catch operation(A, B)) of
    │ │ │ +    {'EXIT', Reason} ->
    │ │ │          ...;
    │ │ │      C ->
    │ │ │          ...
    │ │ │  end

    LNG-003 - Do Not Use the Legacy and and or Operators

    These operators have been superseded by andalso and orelse, respectively.

    The legacy operators have higher precedence than in most other languages. For │ │ │ example X and Y =:= 3 is parsed as (X and Y) =:= 3. In a function body, │ │ │ this will crash, but when used in a guard it will silently fail. It can also │ │ │ unexpectedly corrupt the intended logic without crashing when all operands are │ │ │ @@ -818,38 +818,38 @@ │ │ │ leaking out through a crash or core dump.

    Rule priority: Medium

    Related CWEs and OWASP risks: CWE-209, CWE-532

    MSC-005 - Treat Match Specifications As Code

    Match specifications, such as those used with ETS, are vulnerable to injection │ │ │ attacks if they are constructed based on untrusted input.

    When untrusted data is matched verbatim (such as a key), it is important to │ │ │ wrap it in {const, UntrustedData} expressions. Building general queries based │ │ │ on untrusted data should be avoided, but if that cannot be done, the query │ │ │ should be the result of parsing the untrusted data into a match │ │ │ specification (where the final shape is controlled by the programmer), rather │ │ │ than attempting to validate the data before passing it in unaltered.

    Rule priority: High

    Related CWEs and OWASP risks: CWE-74

    %% DO
    │ │ │ -find(Table, Needle) ->
    │ │ │ -    ets:match(Table, {'_', {const, Needle}, '$1'}).
    │ │ │ +find(Table, Needle) ->
    │ │ │ +    ets:match(Table, {'_', {const, Needle}, '$1'}).
    │ │ │  
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -find(Table, Needle) ->
    │ │ │ -    ets:match(Table, {'_', Needle, '$1'}).

    MSC-006 - Consider "Link Following" Attacks

    When operating on untrusted file paths and trying to access files through them, │ │ │ +find(Table, Needle) -> │ │ │ + ets:match(Table, {'_', Needle, '$1'}).

    MSC-006 - Consider "Link Following" Attacks

    When operating on untrusted file paths and trying to access files through them, │ │ │ it is possible that the name does not actually identify a file, but a link │ │ │ instead, which can in turn point at an unintended resource which is potentially │ │ │ outside of the intended boundaries.

    This can be mitigated by using filelib:safe_relative_path/2 to ensure that │ │ │ the path does not escape the given bounds regardless of links. Note that it is │ │ │ impossible to guarantee atomicity across several filesystem operations, so care │ │ │ must be taken to avoid time-of-check time-of-use (TOCTOU) race conditions where │ │ │ a file or symbolic link is swapped out in the middle of these operations. When │ │ │ operating on a shared folder structure, ensure that only one entity has access │ │ │ to said structure.

    Rule priority: Medium

    Related CWEs and OWASP risks: CWE-22, CWE-59, CWE-61

    %% DO
    │ │ │ -open(UntrustedPath, Root, Opts) ->
    │ │ │ -    case filelib:safe_relative_path(UntrustedPath, Root) of
    │ │ │ -        unsafe -> {error, unsafe};
    │ │ │ -        Path -> file:open(filename:join(Root, Path), Opts)
    │ │ │ +open(UntrustedPath, Root, Opts) ->
    │ │ │ +    case filelib:safe_relative_path(UntrustedPath, Root) of
    │ │ │ +        unsafe -> {error, unsafe};
    │ │ │ +        Path -> file:open(filename:join(Root, Path), Opts)
    │ │ │      end.
    │ │ │  
    │ │ │  %% DO NOT
    │ │ │ -file:open(UntrustedPath, Opts).

    MSC-007 - Avoid Using Debug Functionality in Production

    Functionality that has been explicitly marked to be used only for debugging, │ │ │ +file:open(UntrustedPath, Opts).

    MSC-007 - Avoid Using Debug Functionality in Production

    Functionality that has been explicitly marked to be used only for debugging, │ │ │ such as erlang:list_to_pid/1 or the keep_secrets │ │ │ ssl option should not be used in production environments, except during │ │ │ interactive debugging. Unlike with normal functionality, there are no promises │ │ │ of API stability for debug functionality, and they may change without notice. │ │ │ They sometimes also have adverse effects on system properties while used (such │ │ │ as greatly increasing scheduling latency), which are acceptable during │ │ │ testing but not in production.

    In production environments, debug functionality should be considered unsafe as │ │ ├── ./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 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 can only 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 give an idea about 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 │ │ │

    │ │ │

    Atoms are another data type in Erlang. Atoms start with a lowercase 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 are converted to centimeters and back again, │ │ │ +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 are converted to centimeters and back again, │ │ │ yielding the original value. This also shows that 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 necessary 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 element of the list from the rest of the list, | is │ │ │ -used. First has the value 1 and TheRest has 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 element of the list from the rest of the list, | is │ │ │ +used. First has the value 1 and TheRest has 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 │ │ │ -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 has not already been 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) value to the 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) value to the 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 by default they belong 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 ends 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 a function that doubles the value of a number is defined and assigned 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, []) ->
    │ │ │ +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 library module lists. foreach takes │ │ │ +map(Fun, [First|Rest]) -> │ │ │ + [Fun(First)|map(Fun,Rest)]; │ │ │ +map(Fun, []) -> │ │ │ + [].

    These two functions are provided in the standard library 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 with a fun to add 3 to every element of a list:

    88> Add_3 = fun(X) -> X + 3 end.
    │ │ │ +used with 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 │ │ │ fun Function/Arity (remember that Arity = number of arguments). That is │ │ │ why fun convert_to_c/1 can be used in the call above. 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 following 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 following 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 special 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. 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 handled by the following code:

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

        In the example, system messages are handled 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}.
        │ │ │ +system_get_state(Chs) ->
        │ │ │ +    {ok, 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, Dbg, ...).
        │ │ │ +    Dbg = sys:debug_options([]),
        │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ +    loop(Parent, Module, Dbg, ...).
        │ │ │  
        │ │ │ -...

        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}<br />* Collect Buttons │ │ │ check_code --> locked : Incorrect code │ │ │ check_code --> open : Correct code<br />* do_unlock()<br />* Clear Buttons<br />* Set state_timeout 10 s │ │ │ │ │ │ open --> open : {button, Digit} │ │ │ open --> locked : state_timeout<br />* 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 stores 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/3:

        {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/3:

        {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 and 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 looks as follows:

            #{id => ch3,
            │ │ │ -  start => {ch3, start_link, []},
            │ │ │ +example looks 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 functions 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 dirty │ │ │ 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 of 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 of as │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ according to the type language defined above.

        Built-in typeCan be thought of as 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, they start with an uppercase letter. These variables are 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 arities.

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

        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 are 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 │ │ │ over #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 │ │ │ @@ -167,45 +167,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 │ │ │ @@ -249,18 +249,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-17.0/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,45 +350,45 @@ │ │ │ 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() |
        │ │ │ -fun (DistCtrlr, Node, DHandle, Context) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +following signature:

        fun (DistCtrlr, Node, DHandle) -> void() |
        │ │ │ +fun (DistCtrlr, Node, DHandle, Context) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node connected at the other end. Context is a map of │ │ │ #{creation => Creation, flags => Flags}, and Creation / Flags are the │ │ │ node incarnation identifier / capability flags │ │ │ of the node connected at the other end. 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 - │ │ ├── ./usr/share/doc/erlang-doc/html/erts-17.0/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-17.0/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,57 +680,57 @@ │ │ │ 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 instruction 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 instruction 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:

    • Predicates used by both the interpreter and the JIT implementations │ │ │ are contained in $ERL_TOP/erts/emulator/beam/predicates.tab.

    • Predicates only used by x86_64 JIT can be found in │ │ │ $ERL_TOP/erts/emulator/beam/jit/x86/predicates.tab.

    • Predicates only used by AArc64 (Arm64) JIT can be found in │ │ │ $ERL_TOP/erts/emulator/beam/jit/arm/predicates.tab.

    • Predicates only used by the interpreter can be found in │ │ │ $ERL_TOP/erts/emulator/beam/emu/predicates.tab.

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

    is_map Fail Lit=q | literal_is_map(Lit) => _

    If the Lit operand is a literal, then the literal_is_map() │ │ │ +implementation of a simple predicate called literal_is_map().

    Here is first an example how it is used:

    is_map 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 variable 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 │ │ │ @@ -751,53 +751,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 variable called S, which is a │ │ │ pointer to a state struct. In the example, S is used in the invocation │ │ │ of the NewBeamOp macro.

    │ │ │ │ │ │ │ │ │ @@ -819,431 +819,431 @@ │ │ │ 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 │ │ │ @@ -1251,47 +1251,47 @@ │ │ │ There is one emitter function for each family of specific instructions.

    All source files containing the emitter functions for generating │ │ │ x86_64 native instructions are found in the beam/jit/x86 directory, │ │ │ while the corresponding files for AArch64 (Arm64) are found in │ │ │ beam/jit/arm. Common source code and header files for both back-ends │ │ │ are located in the beam/jit directory.

    Take for example the move instruction. In beam/jit/x86/ops.tab │ │ │ there used to be a single specific instruction for move defined like │ │ │ this:

    move s d

    The implementation found in beam/jit/x86/instr_common.cpp used to │ │ │ -look like this:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ -    mov_arg(Dst, Src);
    │ │ │ -}

    (The current implementation is slightly more complicated because of additional │ │ │ +look like this:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +    mov_arg(Dst, Src);
    │ │ │ +}

    (The current implementation is slightly more complicated because of additional │ │ │ optimizations.)

    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. For example:

    fload S l
    │ │ │ +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. 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<const ArgVal> &args) {
    │ │ │ -    ASSERT(Size.getValue() == args.size());
    │ │ │ +                                          const Span<const 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-17.0/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-17.0/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-17.0/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 │ │ │ @@ -698,15 +698,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>
    │ │ │ @@ -731,30 +731,30 @@
    │ │ │  node.
  • <LogicalIds><ThreadIds><CoreIds><NodeIds><ProcessorIds>, that is, thread │ │ │ is part of a core that is part of a NUMA node, which is part of a │ │ │ processor.
  • A CPU topology can consist of both processor external, and processor │ │ │ internal NUMA nodes as long as each logical processor belongs to only one │ │ │ NUMA node. If <ProcessorIds> is omitted, its default position is before │ │ │ <NodeIds>. That is, the default is processor external NUMA nodes.

    If a list of identifiers is used in an <IdDefs>:

    • <LogicalIds> must be a list of identifiers.
    • At least one other identifier type besides <LogicalIds> must also have a │ │ │ list of identifiers.
    • All lists of identifiers must produce the same number of identifiers.

    A simple example. A single quad core processor can be described as follows:

    % erl +sct L0-3c0-3
    │ │ │ -1> erlang:system_info(cpu_topology).
    │ │ │ -[{processor,[{core,{logical,0}},
    │ │ │ -             {core,{logical,1}},
    │ │ │ -             {core,{logical,2}},
    │ │ │ -             {core,{logical,3}}]}]

    A more complicated example with two quad core processors, each processor in │ │ │ +1> erlang:system_info(cpu_topology). │ │ │ +[{processor,[{core,{logical,0}}, │ │ │ + {core,{logical,1}}, │ │ │ + {core,{logical,2}}, │ │ │ + {core,{logical,3}}]}]

    A more complicated example with two quad core processors, each processor in │ │ │ its own NUMA node. The ordering of logical processors is a bit weird. This │ │ │ to give a better example of identifier lists:

    % erl +sct L0-1,3-2c0-3p0N0:L7,4,6-5c0-3p1N1
    │ │ │ -1> erlang:system_info(cpu_topology).
    │ │ │ -[{node,[{processor,[{core,{logical,0}},
    │ │ │ -                    {core,{logical,1}},
    │ │ │ -                    {core,{logical,3}},
    │ │ │ -                    {core,{logical,2}}]}]},
    │ │ │ - {node,[{processor,[{core,{logical,7}},
    │ │ │ -                    {core,{logical,4}},
    │ │ │ -                    {core,{logical,6}},
    │ │ │ -                    {core,{logical,5}}]}]}]

    As long as real identifiers are correct, it is OK to pass a CPU topology │ │ │ +1> erlang:system_info(cpu_topology). │ │ │ +[{node,[{processor,[{core,{logical,0}}, │ │ │ + {core,{logical,1}}, │ │ │ + {core,{logical,3}}, │ │ │ + {core,{logical,2}}]}]}, │ │ │ + {node,[{processor,[{core,{logical,7}}, │ │ │ + {core,{logical,4}}, │ │ │ + {core,{logical,6}}, │ │ │ + {core,{logical,5}}]}]}]

    As long as real identifiers are correct, it is OK to pass a CPU topology │ │ │ that is not a correct description of the CPU topology. When used with care │ │ │ this can be very useful. This to trick the emulator to bind its schedulers │ │ │ as you want. For example, if you want to run multiple Erlang runtime systems │ │ │ on the same machine, you want to reduce the number of schedulers used and │ │ │ manipulate the CPU topology so that they bind to different logical CPUs. An │ │ │ example, with two Erlang runtime systems on a quad core machine:

    % erl +sct L0-3c0-3 +sbt db +S3:2 -detached -noinput -noshell -sname one
    │ │ │  % erl +sct L3-0c0-3 +sbt db +S3:2 -detached -noinput -noshell -sname two

    In this example, each runtime system have two schedulers each online, and │ │ │ @@ -923,18 +923,18 @@ │ │ │ │ │ │

    The standard Erlang/OTP system can be reconfigured to change the default │ │ │ behavior on startup.

    • The .erlang startup file - When Erlang/OTP is started, the system │ │ │ searches for a file named .erlang in the │ │ │ user's home directory and then │ │ │ filename:basedir(user_config, "erlang").

      If an .erlang file is found, it is assumed to contain valid Erlang │ │ │ expressions. These expressions are evaluated as if they were input to the │ │ │ -shell.

      A typical .erlang file contains a set of search paths, for example:

      io:format("executing user profile in $HOME/.erlang\n",[]).
      │ │ │ -code:add_path("/home/calvin/test/ebin").
      │ │ │ -code:add_path("/home/hobbes/bigappl-1.2/ebin").
      │ │ │ -io:format(".erlang rc finished\n",[]).
    • user_default and shell_default - Functions in the shell that are not │ │ │ +shell.

      A typical .erlang file contains a set of search paths, for example:

      io:format("executing user profile in $HOME/.erlang\n",[]).
      │ │ │ +code:add_path("/home/calvin/test/ebin").
      │ │ │ +code:add_path("/home/hobbes/bigappl-1.2/ebin").
      │ │ │ +io:format(".erlang rc finished\n",[]).
    • user_default and shell_default - Functions in the shell that are not │ │ │ prefixed by a module name are assumed to be functional objects (funs), │ │ │ built-in functions (BIFs), or belong to the module user_default or │ │ │ shell_default.

      To include private shell commands, define them in a module user_default and │ │ │ add the following argument as the first line in the .erlang file:

      code:load_abs("..../user_default").
    • erl - If the contents of .erlang are changed and a private version of │ │ │ user_default is defined, the Erlang/OTP environment can be customized. More │ │ │ powerful changes can be made by supplying command-line arguments in the │ │ │ startup script erl. For more information, see init.

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-17.0/doc/html/erl_dist_protocol.html │ │ │ @@ -252,32 +252,32 @@ │ │ │ --- │ │ │ sequenceDiagram │ │ │ participant client as Client (or Node) │ │ │ participant EPMD │ │ │ │ │ │ client ->> EPMD: EPMD_NAMES_REQ │ │ │ EPMD -->> client: NAMES_RESP

    1
    110

    Table: EPMD_NAMES_REQ (110)

    The response for a EPMD_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: EPMD_DUMP_REQ
    │ │ │      EPMD -->> client: DUMP_RESP
    1
    100

    Table: EPMD_DUMP_REQ

    The response for a EPMD_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-17.0/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-17.0/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-17.0/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.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7027,15 +7027,15 @@ │ │ │

    Computes and returns the adler32 checksum for Data.

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

    │ │ │
    1> Data = ~"abc".
    │ │ │ -2> erlang:adler32(Data).
    │ │ │ +2> erlang:adler32(Data).
    │ │ │  38600999
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Continues computing the adler32 checksum by combining the previous checksum, │ │ │ OldAdler, with the checksum of Data.

    The following code:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:adler32(Data1).
    │ │ │ +2> X = erlang:adler32(Data1).
    │ │ │  38600999
    │ │ │ -3> Y = erlang:adler32(X,Data2).
    │ │ │ +3> Y = erlang:adler32(X,Data2).
    │ │ │  136184406

    assigns the same value to Y as this:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> Y = erlang:adler32([Data1,Data2]).
    │ │ │ +2> Y = erlang:adler32([Data1,Data2]).
    │ │ │  136184406
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    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:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:adler32(Data1).
    │ │ │ +2> X = erlang:adler32(Data1).
    │ │ │  38600999
    │ │ │ -3> Z = erlang:adler32(X,Data2).
    │ │ │ +3> Z = erlang:adler32(X,Data2).
    │ │ │  136184406

    assigns the same value to Z as this:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:adler32(Data1).
    │ │ │ +2> X = erlang:adler32(Data1).
    │ │ │  38600999
    │ │ │ -3> Y = erlang:adler32(Data2).
    │ │ │ +3> Y = erlang:adler32(Data2).
    │ │ │  39780656
    │ │ │ -4> Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).
    │ │ │ +4> Z = erlang:adler32_combine(X,Y,iolist_size(Data2)).
    │ │ │  136184406
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7175,35 +7175,35 @@ │ │ │ call to unalias/1.

  • priority - Since OTP 28.0

    The alias can be used for sending │ │ │ priority messages to the │ │ │ process that created this alias. An alias created with this option is also │ │ │ known as a priority process alias or shorter priority alias.

    Warning

    You very seldom need to resort to using priority messages and you may │ │ │ cause issues │ │ │ instead of solving issues if not used with care.

    For more information see, the │ │ │ Enabling Priority Message Reception │ │ │ -section of the Erlang Reference Manual.

  • Example:

    server() ->
    │ │ │ +section of the Erlang Reference Manual.

    Example:

    server() ->
    │ │ │      receive
    │ │ │ -        {request, AliasReqId, Request} ->
    │ │ │ -            Result = perform_request(Request),
    │ │ │ -            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │ +        {request, AliasReqId, Request} ->
    │ │ │ +            Result = perform_request(Request),
    │ │ │ +            AliasReqId ! {reply, AliasReqId, Result}
    │ │ │      end,
    │ │ │ -    server().
    │ │ │ +    server().
    │ │ │  
    │ │ │ -client(ServerPid, Request) ->
    │ │ │ -    AliasReqId = alias([reply]),
    │ │ │ -    ServerPid ! {request, AliasReqId, Request},
    │ │ │ +client(ServerPid, Request) ->
    │ │ │ +    AliasReqId = alias([reply]),
    │ │ │ +    ServerPid ! {request, AliasReqId, Request},
    │ │ │      %% Alias will be automatically deactivated if we receive a reply
    │ │ │      %% since we used the 'reply' option...
    │ │ │      receive
    │ │ │ -        {reply, AliasReqId, Result} -> Result
    │ │ │ +        {reply, AliasReqId, Result} -> Result
    │ │ │      after 5000 ->
    │ │ │ -            unalias(AliasReqId),
    │ │ │ +            unalias(AliasReqId),
    │ │ │              %% Flush message queue in case the reply arrived
    │ │ │              %% just before the alias was deactivated...
    │ │ │ -            receive {reply, AliasReqId, Result} -> Result
    │ │ │ -            after 0 -> exit(timeout)
    │ │ │ +            receive {reply, AliasReqId, Result} -> Result
    │ │ │ +            after 0 -> exit(timeout)
    │ │ │              end
    │ │ │      end.

    Note that both the server and the client in this example must be executing on at │ │ │ least OTP 24 systems in order for this to work.

    For more information on process aliases see the │ │ │ Process Aliases section of │ │ │ the Erlang Reference Manual.

    │ │ │
    │ │ │ │ │ │ @@ -7235,16 +7235,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}
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7298,17 +7298,17 @@ │ │ │
    -spec apply(Module, Function, Args) -> term()
    │ │ │                 when Module :: module(), Function :: atom(), Args :: [term()].
    │ │ │ │ │ │
    │ │ │ │ │ │

    Returns the result of applying Function in Module to Args. The applied │ │ │ function must be exported from Module. The arity of the function is the length │ │ │ -of Args.

    For example:

    1> apply(lists, reverse, [[a, b, c]]).
    │ │ │ -[c,b,a]
    │ │ │ -2> apply(erlang, atom_to_list, ['Erlang']).
    │ │ │ +of Args.

    For example:

    1> apply(lists, reverse, [[a, b, c]]).
    │ │ │ +[c,b,a]
    │ │ │ +2> apply(erlang, atom_to_list, ['Erlang']).
    │ │ │  "Erlang"

    If the number of arguments are known at compile time, the call is better written │ │ │ as Module:Function(Arg1, Arg2, ..., ArgN).

    Failure: error_handler:undefined_function/3 is called if the applied function │ │ │ is not exported. The error handler can be redefined (see process_flag/2). If │ │ │ error_handler is undefined, or if the user has redefined the default │ │ │ error_handler so the replacement module is undefined, an error with reason │ │ │ undef is generated.

    │ │ │
    │ │ │ @@ -7376,19 +7376,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
    │ │ │ │ │ │ │ │ │
    │ │ │ @@ -7420,20 +7420,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]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7501,21 +7501,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
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7717,19 +7717,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7759,19 +7759,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7800,17 +7800,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.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -7838,16 +7838,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]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7877,15 +7877,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.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8029,17 +8029,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8067,18 +8067,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>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8137,17 +8137,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8267,19 +8267,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8468,15 +8468,15 @@ │ │ │

    Computes and returns the crc32 (IEEE 802.3 style) checksum for Data.

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

    │ │ │
    1> Data = ~"abc".
    │ │ │ -2> erlang:crc32(Data).
    │ │ │ +2> erlang:crc32(Data).
    │ │ │  891568578
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Continues computing the crc32 checksum by combining the previous checksum, │ │ │ OldCrc, with the checksum of Data.

    The following code:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:crc32(Data1).
    │ │ │ +2> X = erlang:crc32(Data1).
    │ │ │  891568578
    │ │ │ -3> Y = erlang:crc32(X,Data2).
    │ │ │ +3> Y = erlang:crc32(X,Data2).
    │ │ │  1267612143

    assigns the same value to Y as this:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> Y = erlang:crc32([Data1,Data2]).
    │ │ │ +2> Y = erlang:crc32([Data1,Data2]).
    │ │ │  1267612143
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    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:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:crc32(Data1).
    │ │ │ +2> X = erlang:crc32(Data1).
    │ │ │  891568578
    │ │ │ -3> Y = erlang:crc32(X,Data2).
    │ │ │ +3> Y = erlang:crc32(X,Data2).
    │ │ │  1267612143

    assigns the same value to Z as this:

    1> Data1 = ~"abc", Data2 = ~"def".
    │ │ │ -2> X = erlang:crc32(Data1).
    │ │ │ +2> X = erlang:crc32(Data1).
    │ │ │  891568578
    │ │ │ -3> Y = erlang:crc32(Data2).
    │ │ │ +3> Y = erlang:crc32(Data2).
    │ │ │  214229345
    │ │ │ -4> Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).
    │ │ │ +4> Z = erlang:crc32_combine(X,Y,iolist_size(Data2)).
    │ │ │  1267612143
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ │ │ │

    Returns the current date as {Year, Month, Day}.

    The time zone and Daylight Saving Time correction depend on the underlying OS. │ │ │ The return value is based on the │ │ │ -OS System Time.

    For example:

    > date().
    │ │ │ -{1995,2,19}
    │ │ │ +OS System Time.

    For example:

    > date().
    │ │ │ +{1995,2,19}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8687,18 +8687,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}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8725,16 +8725,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}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8836,17 +8836,17 @@ │ │ │ when MonitorRef :: reference(), OptionList :: [Option], Option :: flush | info.
    │ │ │ │ │ │ │ │ │ │ │ │

    The returned value is true unless info is part of OptionList.

    demonitor(MonitorRef, []) is equivalent to │ │ │ demonitor(MonitorRef).

    Options:

    • flush - Removes (one) {_, MonitorRef, _, _, _} message, if there is │ │ │ one, from the caller message queue after monitoring has been stopped.

      Calling demonitor(MonitorRef, [flush]) is equivalent to the │ │ │ -following, but more efficient:

      demonitor(MonitorRef),
      │ │ │ +following, but more efficient:

      demonitor(MonitorRef),
      │ │ │  receive
      │ │ │ -    {_, MonitorRef, _, _, _} ->
      │ │ │ +    {_, MonitorRef, _, _, _} ->
      │ │ │          true
      │ │ │  after 0 ->
      │ │ │          true
      │ │ │  end
    • info - The returned value is one of the following: